diff options
author | 2023-07-11 19:14:34 -0700 | |
---|---|---|
committer | 2023-07-11 19:14:34 -0700 | |
commit | cbb88672f217a90db1aa1eb29cd92d5d9035b22b (patch) | |
tree | 43a00501f3cde495967e116f0b660777051551f8 /src/bun.js/api | |
parent | 1f900cff453700b19bca2acadfe26da4468c1282 (diff) | |
parent | 34b0e7a2bbd8bf8097341cdb0075d0908283e834 (diff) | |
download | bun-jarred/esm-conditions.tar.gz bun-jarred/esm-conditions.tar.zst bun-jarred/esm-conditions.zip |
Merge branch 'main' into jarred/esm-conditionsjarred/esm-conditions
Diffstat (limited to 'src/bun.js/api')
-rw-r--r-- | src/bun.js/api/JSBundler.zig | 47 | ||||
-rw-r--r-- | src/bun.js/api/JSTranspiler.zig | 10 | ||||
-rw-r--r-- | src/bun.js/api/bun.zig | 205 | ||||
-rw-r--r-- | src/bun.js/api/bun/dns_resolver.zig | 22 | ||||
-rw-r--r-- | src/bun.js/api/bun/socket.zig | 601 | ||||
-rw-r--r-- | src/bun.js/api/bun/subprocess.zig | 6 | ||||
-rw-r--r-- | src/bun.js/api/ffi.zig | 36 | ||||
-rw-r--r-- | src/bun.js/api/html_rewriter.zig | 169 | ||||
-rw-r--r-- | src/bun.js/api/server.zig | 443 | ||||
-rw-r--r-- | src/bun.js/api/sockets.classes.ts | 12 |
10 files changed, 1032 insertions, 519 deletions
diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig index 8e85f1190..44ceaee9d 100644 --- a/src/bun.js/api/JSBundler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -26,7 +26,7 @@ const strings = bun.strings; const NewClass = Base.NewClass; const To = Base.To; const Request = WebCore.Request; - +const String = bun.String; const FetchEvent = WebCore.FetchEvent; const MacroMap = @import("../../resolver/package_json.zig").MacroMap; const TSConfigJSON = @import("../../resolver/tsconfig_json.zig").TSConfigJSON; @@ -844,7 +844,7 @@ pub const JSBundler = struct { this.value = .{ .success = .{ - .loader = @intToEnum(options.Loader, @intCast(u8, loader_as_int.to(i32))), + .loader = @enumFromInt(options.Loader, @intCast(u8, loader_as_int.to(i32))), .source_code = source_code, }, }; @@ -871,16 +871,16 @@ pub const JSBundler = struct { extern fn JSBundlerPlugin__anyMatches( *Plugin, - namespaceString: *const ZigString, - path: *const ZigString, + namespaceString: *const String, + path: *const String, bool, ) bool; extern fn JSBundlerPlugin__matchOnLoad( *JSC.JSGlobalObject, *Plugin, - namespaceString: *const ZigString, - path: *const ZigString, + namespaceString: *const String, + path: *const String, context: *anyopaque, u8, ) void; @@ -888,9 +888,9 @@ pub const JSBundler = struct { extern fn JSBundlerPlugin__matchOnResolve( *JSC.JSGlobalObject, *Plugin, - namespaceString: *const ZigString, - path: *const ZigString, - importer: *const ZigString, + namespaceString: *const String, + path: *const String, + importer: *const String, context: *anyopaque, u8, ) void; @@ -905,10 +905,10 @@ pub const JSBundler = struct { defer tracer.end(); const namespace_string = if (path.isFile()) - ZigString.Empty + bun.String.empty else - ZigString.fromUTF8(path.namespace); - const path_string = ZigString.fromUTF8(path.text); + bun.String.create(path.namespace); + const path_string = bun.String.create(path.text); return JSBundlerPlugin__anyMatches(this, &namespace_string, &path_string, is_onLoad); } @@ -924,11 +924,13 @@ pub const JSBundler = struct { const tracer = bun.tracy.traceNamed(@src(), "JSBundler.matchOnLoad"); defer tracer.end(); const namespace_string = if (namespace.len == 0) - ZigString.init("file") + bun.String.static("file") else - ZigString.fromUTF8(namespace); - const path_string = ZigString.fromUTF8(path); - JSBundlerPlugin__matchOnLoad(globalThis, this, &namespace_string, &path_string, context, @enumToInt(default_loader)); + bun.String.create(namespace); + const path_string = bun.String.create(path); + defer namespace_string.deref(); + defer path_string.deref(); + JSBundlerPlugin__matchOnLoad(globalThis, this, &namespace_string, &path_string, context, @intFromEnum(default_loader)); } pub fn matchOnResolve( @@ -944,12 +946,15 @@ pub const JSBundler = struct { const tracer = bun.tracy.traceNamed(@src(), "JSBundler.matchOnResolve"); defer tracer.end(); const namespace_string = if (strings.eqlComptime(namespace, "file")) - ZigString.Empty + bun.String.empty else - ZigString.fromUTF8(namespace); - const path_string = ZigString.fromUTF8(path); - const importer_string = ZigString.fromUTF8(importer); - JSBundlerPlugin__matchOnResolve(globalThis, this, &namespace_string, &path_string, &importer_string, context, @enumToInt(import_record_kind)); + bun.String.create(namespace); + const path_string = bun.String.create(path); + const importer_string = bun.String.create(importer); + defer namespace_string.deref(); + defer path_string.deref(); + defer importer_string.deref(); + JSBundlerPlugin__matchOnResolve(globalThis, this, &namespace_string, &path_string, &importer_string, context, @intFromEnum(import_record_kind)); } pub fn addPlugin( diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index a1e1cfa36..308738abf 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -85,7 +85,7 @@ const TranspilerOptions = struct { // This is going to be hard to not leak pub const TransformTask = struct { input_code: ZigString = ZigString.init(""), - protected_input_value: JSC.JSValue = @intToEnum(JSC.JSValue, 0), + protected_input_value: JSC.JSValue = @enumFromInt(JSC.JSValue, 0), output_code: ZigString = ZigString.init(""), bundler: Bundler.Bundler = undefined, log: logger.Log, @@ -220,8 +220,8 @@ pub const TransformTask = struct { finish(this.output_code, this.global, promise); - if (@enumToInt(this.protected_input_value) != 0) { - this.protected_input_value = @intToEnum(JSC.JSValue, 0); + if (@intFromEnum(this.protected_input_value) != 0) { + this.protected_input_value = @enumFromInt(JSC.JSValue, 0); } this.deinit(); } @@ -611,7 +611,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std while (length_iter.next()) |value| { if (value.isString()) { const length = @truncate(u32, value.getLength(globalThis)); - string_count += @as(u32, @boolToInt(length > 0)); + string_count += @as(u32, @intFromBool(length > 0)); total_name_buf_len += length; } } @@ -877,7 +877,7 @@ fn getParseResult(this: *Transpiler, allocator: std.mem.Allocator, code: []const for (res.ast.import_records.slice()) |*import| { if (import.kind.isCommonJS()) { import.do_commonjs_transform_in_printer = true; - import.module_id = @truncate(u32, std.hash.Wyhash.hash(0, import.path.pretty)); + import.module_id = @truncate(u32, bun.hash(import.path.pretty)); } } } diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 5580e8840..fbf567446 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -303,7 +303,7 @@ pub fn registerMacro( return js.JSValueMakeUndefined(ctx); } // TODO: make this faster - const id = @truncate(i32, @floatToInt(i64, js.JSValueToNumber(ctx, arguments[0], exception))); + const id = @truncate(i32, @intFromFloat(i64, js.JSValueToNumber(ctx, arguments[0], exception))); if (id == -1 or id == 0) { JSError(getAllocator(ctx), "Internal error registering macros: invalid id", .{}, ctx, exception); return js.JSValueMakeUndefined(ctx); @@ -523,7 +523,7 @@ pub fn getFilePath(ctx: js.JSContextRef, arguments: []const js.JSValueRef, buf: temp_strings_list[temp_strings_list_len] = out_slice; // The dots are kind of unnecessary. They'll be normalized. - if (out.len == 0 or @ptrToInt(out.ptr) == 0 or std.mem.eql(u8, out_slice, ".") or std.mem.eql(u8, out_slice, "..") or std.mem.eql(u8, out_slice, "../")) { + if (out.len == 0 or @intFromPtr(out.ptr) == 0 or std.mem.eql(u8, out_slice, ".") or std.mem.eql(u8, out_slice, "..") or std.mem.eql(u8, out_slice, "../")) { JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception); return null; } @@ -600,7 +600,7 @@ pub fn readFileAsStringCallback( return js.JSValueMakeUndefined(ctx); }; - if (stat.kind != .File) { + if (stat.kind != .file) { JSError(getAllocator(ctx), "Can't read a {s} as a string (\"{s}\")", .{ @tagName(stat.kind), path }, ctx, exception); return js.JSValueMakeUndefined(ctx); } @@ -641,7 +641,7 @@ pub fn readFileAsBytesCallback( return js.JSValueMakeUndefined(ctx); }; - if (stat.kind != .File) { + if (stat.kind != .file) { JSError(allocator, "Can't read a {s} as a string (\"{s}\")", .{ @tagName(stat.kind), path }, ctx, exception); return js.JSValueMakeUndefined(ctx); } @@ -896,6 +896,9 @@ pub fn createNodeFS( ) js.JSValueRef { var module = ctx.allocator().create(JSC.Node.NodeJSFS) catch unreachable; module.* = .{}; + var vm = ctx.bunVM(); + if (vm.standalone_module_graph != null) + module.node_fs.vm = vm; return module.toJS(ctx).asObjectRef(); } @@ -1612,7 +1615,7 @@ pub const Crypto = struct { fn createCryptoError(globalThis: *JSC.JSGlobalObject, err_code: u32) JSValue { var outbuf: [128 + 1 + "BoringSSL error: ".len]u8 = undefined; - @memset(&outbuf, 0, outbuf.len); + @memset(&outbuf, 0); outbuf[0.."BoringSSL error: ".len].* = "BoringSSL error: ".*; var message_buf = outbuf["BoringSSL error: ".len..]; @@ -3171,9 +3174,9 @@ pub fn mmapFile( return JSC.C.JSObjectMakeTypedArrayWithBytesNoCopy(ctx, JSC.C.JSTypedArrayType.kJSTypedArrayTypeUint8Array, @ptrCast(?*anyopaque, map.ptr), map.len, struct { pub fn x(ptr: ?*anyopaque, size: ?*anyopaque) callconv(.C) void { - _ = JSC.Node.Syscall.munmap(@ptrCast([*]align(std.mem.page_size) u8, @alignCast(std.mem.page_size, ptr))[0..@ptrToInt(size)]); + _ = JSC.Node.Syscall.munmap(@ptrCast([*]align(std.mem.page_size) u8, @alignCast(std.mem.page_size, ptr))[0..@intFromPtr(size)]); } - }.x, @intToPtr(?*anyopaque, map.len), exception); + }.x, @ptrFromInt(?*anyopaque, map.len), exception); } pub fn getTranspilerConstructor( @@ -3401,7 +3404,7 @@ pub const Unsafe = struct { globalThis: *JSC.JSGlobalObject, value_: ?JSValue, ) JSValue { - const ret = JSValue.jsNumber(@as(i32, @enumToInt(globalThis.bunVM().aggressive_garbage_collection))); + const ret = JSValue.jsNumber(@as(i32, @intFromEnum(globalThis.bunVM().aggressive_garbage_collection))); if (value_) |value| { switch (value.coerce(i32, globalThis)) { @@ -3712,21 +3715,32 @@ pub const Timer = struct { const kind = this.kind; var map: *TimeoutMap = vm.timer.maps.get(kind); - // This doesn't deinit the timer - // Timers are deinit'd separately - // We do need to handle when the timer is cancelled after the job has been enqueued - if (kind != .setInterval) { - if (map.fetchSwapRemove(this.id) == null) { - // if the timeout was cancelled, don't run the callback - this.deinit(); - return; - } - } else { - if (!map.contains(this.id)) { - // if the interval was cancelled, don't run the callback - this.deinit(); - return; + const should_cancel_job = brk: { + // This doesn't deinit the timer + // Timers are deinit'd separately + // We do need to handle when the timer is cancelled after the job has been enqueued + if (kind != .setInterval) { + if (map.get(this.id)) |tombstone_or_timer| { + break :brk tombstone_or_timer != null; + } else { + // clearTimeout has been called + break :brk true; + } + } else { + if (map.get(this.id)) |tombstone_or_timer| { + // .refresh() was called after CallbackJob enqueued + break :brk tombstone_or_timer == null; + } } + + break :brk false; + }; + + if (should_cancel_job) { + this.deinit(); + return; + } else if (kind != .setInterval) { + _ = map.swapRemove(this.id); } var args_buf: [8]JSC.JSValue = undefined; @@ -3791,6 +3805,8 @@ pub const Timer = struct { result.then(globalThis, this, CallbackJob__onResolve, CallbackJob__onReject); }, } + } else { + this.deinit(); } } }; @@ -3820,10 +3836,29 @@ pub const Timer = struct { return timer_js; } - pub fn doRef(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + pub fn doRef(this: *TimerObject, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { + const this_value = callframe.this(); + this_value.ensureStillAlive(); if (this.ref_count > 0) this.ref_count +|= 1; - return JSValue.jsUndefined(); + + var vm = globalObject.bunVM(); + switch (this.kind) { + .setTimeout, .setImmediate, .setInterval => { + if (vm.timer.maps.get(this.kind).getPtr(this.id)) |val_| { + if (val_.*) |*val| { + val.poll_ref.ref(vm); + + if (val.did_unref_timer) { + val.did_unref_timer = false; + vm.uws_event_loop.?.num_polls += 1; + } + } + } + }, + } + + return this_value; } pub fn doRefresh(this: *TimerObject, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { @@ -3912,27 +3947,34 @@ pub const Timer = struct { id, Timeout.run, this.interval, - @as(i32, @boolToInt(this.kind == .setInterval)) * this.interval, + @as(i32, @intFromBool(this.kind == .setInterval)) * this.interval, ); return this_value; } return JSValue.jsUndefined(); } - pub fn doUnref(this: *TimerObject, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + pub fn doUnref(this: *TimerObject, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { + const this_value = callframe.this(); + this_value.ensureStillAlive(); this.ref_count -|= 1; - if (this.ref_count == 0) { - switch (this.kind) { - .setTimeout, .setImmediate => { - _ = clearTimeout(globalObject, JSValue.jsNumber(this.id)); - }, - .setInterval => { - _ = clearInterval(globalObject, JSValue.jsNumber(this.id)); - }, - } + var vm = globalObject.bunVM(); + switch (this.kind) { + .setTimeout, .setImmediate, .setInterval => { + if (vm.timer.maps.get(this.kind).getPtr(this.id)) |val_| { + if (val_.*) |*val| { + val.poll_ref.unref(vm); + + if (!val.did_unref_timer) { + val.did_unref_timer = true; + vm.uws_event_loop.?.num_polls -= 1; + } + } + } + }, } - return JSValue.jsUndefined(); + return this_value; } pub fn hasRef(this: *TimerObject, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { return JSValue.jsBoolean(this.ref_count > 0 and globalObject.bunVM().timer.maps.get(this.kind).contains(this.id)); @@ -3954,6 +3996,7 @@ pub const Timer = struct { callback: JSC.Strong = .{}, globalThis: *JSC.JSGlobalObject, timer: *uws.Timer, + did_unref_timer: bool = false, poll_ref: JSC.PollRef = JSC.PollRef.init(), arguments: JSC.Strong = .{}, @@ -4055,8 +4098,14 @@ pub const Timer = struct { var vm = this.globalThis.bunVM(); - this.poll_ref.unrefOnNextTick(vm); + this.poll_ref.unref(vm); + this.timer.deinit(); + if (this.did_unref_timer) { + // balance double-unrefing + vm.uws_event_loop.?.num_polls += 1; + } + this.callback.deinit(); this.arguments.deinit(); } @@ -4130,7 +4179,7 @@ pub const Timer = struct { }, Timeout.run, interval, - @as(i32, @boolToInt(kind == .setInterval)) * interval, + @as(i32, @intFromBool(kind == .setInterval)) * interval, ); } @@ -4318,7 +4367,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) u8, addr).*; + const value = @ptrFromInt(*align(1) u8, addr).*; return JSValue.jsNumber(value); } pub fn @"u16"( @@ -4327,7 +4376,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) u16, addr).*; + const value = @ptrFromInt(*align(1) u16, addr).*; return JSValue.jsNumber(value); } pub fn @"u32"( @@ -4336,7 +4385,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) u32, addr).*; + const value = @ptrFromInt(*align(1) u32, addr).*; return JSValue.jsNumber(value); } pub fn ptr( @@ -4345,7 +4394,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) u64, addr).*; + const value = @ptrFromInt(*align(1) u64, addr).*; return JSValue.jsNumber(value); } pub fn @"i8"( @@ -4354,7 +4403,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) i8, addr).*; + const value = @ptrFromInt(*align(1) i8, addr).*; return JSValue.jsNumber(value); } pub fn @"i16"( @@ -4363,7 +4412,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) i16, addr).*; + const value = @ptrFromInt(*align(1) i16, addr).*; return JSValue.jsNumber(value); } pub fn @"i32"( @@ -4372,7 +4421,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) i32, addr).*; + const value = @ptrFromInt(*align(1) i32, addr).*; return JSValue.jsNumber(value); } pub fn intptr( @@ -4381,7 +4430,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) i64, addr).*; + const value = @ptrFromInt(*align(1) i64, addr).*; return JSValue.jsNumber(value); } @@ -4391,7 +4440,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) f32, addr).*; + const value = @ptrFromInt(*align(1) f32, addr).*; return JSValue.jsNumber(value); } @@ -4401,7 +4450,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) f64, addr).*; + const value = @ptrFromInt(*align(1) f64, addr).*; return JSValue.jsNumber(value); } @@ -4411,7 +4460,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) i64, addr).*; + const value = @ptrFromInt(*align(1) i64, addr).*; return JSValue.fromInt64NoTruncate(global, value); } @@ -4421,7 +4470,7 @@ pub const FFI = struct { arguments: []const JSValue, ) JSValue { const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @intCast(usize, arguments[1].to(i32)) else @as(usize, 0); - const value = @intToPtr(*align(1) u64, addr).*; + const value = @ptrFromInt(*align(1) u64, addr).*; return JSValue.fromUInt64NoTruncate(global, value); } @@ -4432,7 +4481,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) u8, addr).*; + const value = @ptrFromInt(*align(1) u8, addr).*; return JSValue.jsNumber(value); } pub fn u16WithoutTypeChecks( @@ -4442,7 +4491,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) u16, addr).*; + const value = @ptrFromInt(*align(1) u16, addr).*; return JSValue.jsNumber(value); } pub fn u32WithoutTypeChecks( @@ -4452,7 +4501,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) u32, addr).*; + const value = @ptrFromInt(*align(1) u32, addr).*; return JSValue.jsNumber(value); } pub fn ptrWithoutTypeChecks( @@ -4462,7 +4511,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) u64, addr).*; + const value = @ptrFromInt(*align(1) u64, addr).*; return JSValue.jsNumber(value); } pub fn i8WithoutTypeChecks( @@ -4472,7 +4521,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) i8, addr).*; + const value = @ptrFromInt(*align(1) i8, addr).*; return JSValue.jsNumber(value); } pub fn i16WithoutTypeChecks( @@ -4482,7 +4531,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) i16, addr).*; + const value = @ptrFromInt(*align(1) i16, addr).*; return JSValue.jsNumber(value); } pub fn i32WithoutTypeChecks( @@ -4492,7 +4541,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) i32, addr).*; + const value = @ptrFromInt(*align(1) i32, addr).*; return JSValue.jsNumber(value); } pub fn intptrWithoutTypeChecks( @@ -4502,7 +4551,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) i64, addr).*; + const value = @ptrFromInt(*align(1) i64, addr).*; return JSValue.jsNumber(value); } @@ -4513,7 +4562,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) f32, addr).*; + const value = @ptrFromInt(*align(1) f32, addr).*; return JSValue.jsNumber(value); } @@ -4524,7 +4573,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) f64, addr).*; + const value = @ptrFromInt(*align(1) f64, addr).*; return JSValue.jsNumber(value); } @@ -4535,7 +4584,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) u64, addr).*; + const value = @ptrFromInt(*align(1) u64, addr).*; return JSValue.fromUInt64NoTruncate(global, value); } @@ -4546,7 +4595,7 @@ pub const FFI = struct { offset: i32, ) callconv(.C) JSValue { const addr = @intCast(usize, raw_addr) + @intCast(usize, offset); - const value = @intToPtr(*align(1) i64, addr).*; + const value = @ptrFromInt(*align(1) i64, addr).*; return JSValue.fromInt64NoTruncate(global, value); } @@ -4590,7 +4639,7 @@ pub const FFI = struct { _: *anyopaque, array: *JSC.JSUint8Array, ) callconv(.C) JSValue { - return JSValue.fromPtrAddress(@ptrToInt(array.ptr())); + return JSValue.fromPtrAddress(@intFromPtr(array.ptr())); } fn ptr_( @@ -4610,9 +4659,9 @@ pub const FFI = struct { return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis); } - var addr: usize = @ptrToInt(array_buffer.ptr); + var addr: usize = @intFromPtr(array_buffer.ptr); // const Sizes = @import("../bindings/sizes.zig"); - // std.debug.assert(addr == @ptrToInt(value.asEncoded().ptr) + Sizes.Bun_FFI_PointerOffsetToTypedArrayVector); + // std.debug.assert(addr == @intFromPtr(value.asEncoded().ptr) + Sizes.Bun_FFI_PointerOffsetToTypedArrayVector); if (byteOffset) |off| { if (!off.isEmptyOrUndefinedOrNull()) { @@ -4628,7 +4677,7 @@ pub const FFI = struct { addr += @intCast(usize, bytei64); } - if (addr > @ptrToInt(array_buffer.ptr) + @as(usize, array_buffer.byte_len)) { + if (addr > @intFromPtr(array_buffer.ptr) + @as(usize, array_buffer.byte_len)) { return JSC.toInvalidArguments("byteOffset out of bounds", .{}, globalThis); } } @@ -4720,11 +4769,11 @@ pub const FFI = struct { } const length = @intCast(usize, length_i); - return .{ .slice = @intToPtr([*]u8, addr)[0..length] }; + return .{ .slice = @ptrFromInt([*]u8, addr)[0..length] }; } } - return .{ .slice = bun.span(@intToPtr([*:0]u8, addr)) }; + return .{ .slice = bun.span(@ptrFromInt([*:0]u8, addr)) }; } fn getCPtr(value: JSValue) ?usize { @@ -4759,11 +4808,11 @@ pub const FFI = struct { var ctx: ?*anyopaque = null; if (finalizationCallback) |callback_value| { if (getCPtr(callback_value)) |callback_ptr| { - callback = @intToPtr(JSC.C.JSTypedArrayBytesDeallocator, callback_ptr); + callback = @ptrFromInt(JSC.C.JSTypedArrayBytesDeallocator, callback_ptr); if (finalizationCtxOrPtr) |ctx_value| { if (getCPtr(ctx_value)) |ctx_ptr| { - ctx = @intToPtr(*anyopaque, ctx_ptr); + ctx = @ptrFromInt(*anyopaque, ctx_ptr); } else if (!ctx_value.isUndefinedOrNull()) { return JSC.toInvalidArguments("Expected user data to be a C pointer (number or BigInt)", .{}, globalThis); } @@ -4773,7 +4822,7 @@ pub const FFI = struct { } } else if (finalizationCtxOrPtr) |callback_value| { if (getCPtr(callback_value)) |callback_ptr| { - callback = @intToPtr(JSC.C.JSTypedArrayBytesDeallocator, callback_ptr); + callback = @ptrFromInt(JSC.C.JSTypedArrayBytesDeallocator, callback_ptr); } else if (!callback_value.isEmptyOrUndefinedOrNull()) { return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis); } @@ -4801,11 +4850,11 @@ pub const FFI = struct { var ctx: ?*anyopaque = null; if (finalizationCallback) |callback_value| { if (getCPtr(callback_value)) |callback_ptr| { - callback = @intToPtr(JSC.C.JSTypedArrayBytesDeallocator, callback_ptr); + callback = @ptrFromInt(JSC.C.JSTypedArrayBytesDeallocator, callback_ptr); if (finalizationCtxOrPtr) |ctx_value| { if (getCPtr(ctx_value)) |ctx_ptr| { - ctx = @intToPtr(*anyopaque, ctx_ptr); + ctx = @ptrFromInt(*anyopaque, ctx_ptr); } else if (!ctx_value.isEmptyOrUndefinedOrNull()) { return JSC.toInvalidArguments("Expected user data to be a C pointer (number or BigInt)", .{}, globalThis); } @@ -4815,7 +4864,7 @@ pub const FFI = struct { } } else if (finalizationCtxOrPtr) |callback_value| { if (getCPtr(callback_value)) |callback_ptr| { - callback = @intToPtr(JSC.C.JSTypedArrayBytesDeallocator, callback_ptr); + callback = @ptrFromInt(JSC.C.JSTypedArrayBytesDeallocator, callback_ptr); } else if (!callback_value.isEmptyOrUndefinedOrNull()) { return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis); } @@ -4935,11 +4984,11 @@ pub const EnvironmentVariables = struct { pub fn getEnvNames(globalObject: *JSC.JSGlobalObject, names: []ZigString) usize { var vm = globalObject.bunVM(); const keys = vm.bundler.env.map.map.keys(); - const max = @min(names.len, keys.len); - for (keys[0..max], 0..) |key, i| { - names[i] = ZigString.initUTF8(key); + const len = @min(names.len, keys.len); + for (keys[0..len], names[0..len]) |key, *name| { + name.* = ZigString.initUTF8(key); } - return keys.len; + return len; } pub fn getEnvValue(globalObject: *JSC.JSGlobalObject, name: ZigString) ?ZigString { var vm = globalObject.bunVM(); diff --git a/src/bun.js/api/bun/dns_resolver.zig b/src/bun.js/api/bun/dns_resolver.zig index aec295056..d0d4f5b7b 100644 --- a/src/bun.js/api/bun/dns_resolver.zig +++ b/src/bun.js/api/bun/dns_resolver.zig @@ -123,7 +123,7 @@ const LibInfo = struct { this.vm.uws_event_loop.?, .machport, true, - @ptrToInt(request.backend.libinfo.machport), + @intFromPtr(request.backend.libinfo.machport), ) == .result, ); @@ -230,7 +230,7 @@ fn addrInfoCount(addrinfo: *std.c.addrinfo) u32 { var count: u32 = 1; var current: ?*std.c.addrinfo = addrinfo.next; while (current != null) : (current = current.?.next) { - count += @boolToInt(current.?.addr != null); + count += @intFromBool(current.?.addr != null); } return count; } @@ -285,7 +285,7 @@ pub const GetAddrInfo = struct { pub fn toCAres(this: GetAddrInfo) bun.c_ares.AddrInfo_hints { var hints: bun.c_ares.AddrInfo_hints = undefined; - @memset(std.mem.asBytes(&hints), 0, @sizeOf(bun.c_ares.AddrInfo_hints)); + @memset(std.mem.asBytes(&hints)[0..@sizeOf(bun.c_ares.AddrInfo_hints)], 0); hints.ai_family = this.options.family.toLibC(); hints.ai_socktype = this.options.socktype.toLibC(); @@ -320,7 +320,7 @@ pub const GetAddrInfo = struct { } var hints: std.c.addrinfo = undefined; - @memset(std.mem.asBytes(&hints), 0, @sizeOf(std.c.addrinfo)); + @memset(std.mem.asBytes(&hints)[0..@sizeOf(std.c.addrinfo)], 0); hints.family = this.family.toLibC(); hints.socktype = this.socktype.toLibC(); @@ -793,7 +793,7 @@ pub const GetAddrInfoRequest = struct { addr_info: ?*std.c.addrinfo, arg: ?*anyopaque, ) callconv(.C) void { - const this = @intToPtr(*GetAddrInfoRequest, @ptrToInt(arg)); + const this = @ptrFromInt(*GetAddrInfoRequest, @intFromPtr(arg)); log("getAddrInfoAsyncCallback: status={d}", .{status}); if (this.backend == .libinfo) { @@ -846,8 +846,8 @@ pub const GetAddrInfoRequest = struct { err, debug_timer, }); - if (@enumToInt(err) != 0 or addrinfo == null) { - this.* = .{ .err = @enumToInt(err) }; + if (@intFromEnum(err) != 0 or addrinfo == null) { + this.* = .{ .err = @intFromEnum(err) }; return; } @@ -1925,8 +1925,8 @@ pub const DNSResolver = struct { .err => |err| { const system_error = JSC.SystemError{ .errno = -1, - .code = JSC.ZigString.init(err.code()), - .message = JSC.ZigString.init(err.label()), + .code = bun.String.static(err.code()), + .message = bun.String.static(err.label()), }; globalThis.throwValue(system_error.toErrorInstance(globalThis)); @@ -1972,8 +1972,8 @@ pub const DNSResolver = struct { .err => |err| { const system_error = JSC.SystemError{ .errno = -1, - .code = JSC.ZigString.init(err.code()), - .message = JSC.ZigString.init(err.label()), + .code = bun.String.static(err.code()), + .message = bun.String.static(err.label()), }; globalThis.throwValue(system_error.toErrorInstance(globalThis)); diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 48bfe4218..1d85c705c 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -69,6 +69,11 @@ fn normalizeHost(input: anytype) @TypeOf(input) { const BinaryType = JSC.BinaryType; +const WrappedType = enum { + none, + tls, + tcp, +}; const Handlers = struct { onOpen: JSC.JSValue = .zero, onClose: JSC.JSValue = .zero, @@ -97,8 +102,8 @@ const Handlers = struct { handlers: *Handlers, socket_context: *uws.SocketContext, - pub fn exit(this: *Scope, ssl: bool) void { - this.handlers.markInactive(ssl, this.socket_context); + pub fn exit(this: *Scope, ssl: bool, wrapped: WrappedType) void { + this.handlers.markInactive(ssl, this.socket_context, wrapped); } }; @@ -123,19 +128,24 @@ const Handlers = struct { return true; } - pub fn markInactive(this: *Handlers, ssl: bool, ctx: *uws.SocketContext) void { + pub fn markInactive(this: *Handlers, ssl: bool, ctx: *uws.SocketContext, wrapped: WrappedType) void { Listener.log("markInactive", .{}); this.active_connections -= 1; - if (this.active_connections == 0 and this.is_server) { - var listen_socket: *Listener = @fieldParentPtr(Listener, "handlers", this); - // allow it to be GC'd once the last connection is closed and it's not listening anymore - if (listen_socket.listener == null) { - listen_socket.strong_self.clear(); + if (this.active_connections == 0) { + if (this.is_server) { + var listen_socket: *Listener = @fieldParentPtr(Listener, "handlers", this); + // allow it to be GC'd once the last connection is closed and it's not listening anymore + if (listen_socket.listener == null) { + listen_socket.strong_self.clear(); + } + } else { + this.unprotect(); + // will deinit when is not wrapped or when is the TCP wrapped connection + if (wrapped != .tls) { + ctx.deinit(ssl); + } + bun.default_allocator.destroy(this); } - } else if (this.active_connections == 0 and !this.is_server) { - this.unprotect(); - ctx.deinit(ssl); - bun.default_allocator.destroy(this); } } @@ -364,6 +374,7 @@ pub const Listener = struct { connection: UnixOrHost, socket_context: ?*uws.SocketContext = null, ssl: bool = false, + protos: ?[]const u8 = null, strong_data: JSC.Strong = .{}, strong_self: JSC.Strong = .{}, @@ -395,13 +406,26 @@ pub const Listener = struct { port: u16, }, + pub fn clone(this: UnixOrHost) UnixOrHost { + switch (this) { + .unix => |u| { + return .{ + .unix = (bun.default_allocator.dupe(u8, u) catch unreachable), + }; + }, + .host => |h| { + return .{ .host = .{ .host = (bun.default_allocator.dupe(u8, h.host) catch unreachable), .port = this.host.port } }; + }, + } + } + pub fn deinit(this: UnixOrHost) void { switch (this) { .unix => |u| { - bun.default_allocator.destroy(@intToPtr([*]u8, @ptrToInt(u.ptr))); + bun.default_allocator.destroy(@ptrFromInt([*]u8, @intFromPtr(u.ptr))); }, .host => |h| { - bun.default_allocator.destroy(@intToPtr([*]u8, @ptrToInt(h.host.ptr))); + bun.default_allocator.destroy(@ptrFromInt([*]u8, @intFromPtr(h.host.ptr))); }, } } @@ -455,10 +479,12 @@ pub const Listener = struct { var socket_config = SocketConfig.fromJS(opts, globalObject, exception) orelse { return .zero; }; + var hostname_or_unix = socket_config.hostname_or_unix; var port = socket_config.port; var ssl = socket_config.ssl; var handlers = socket_config.handlers; + var protos: ?[]const u8 = null; const exclusive = socket_config.exclusive; handlers.is_server = true; @@ -472,7 +498,7 @@ pub const Listener = struct { globalObject.bunVM().eventLoop().ensureWaker(); var socket_context = uws.us_create_bun_socket_context( - @boolToInt(ssl_enabled), + @intFromBool(ssl_enabled), uws.Loop.get().?, @sizeOf(usize), ctx_opts, @@ -483,7 +509,7 @@ pub const Listener = struct { hostname_or_unix.deinit(); } - const errno = @enumToInt(std.c.getErrno(-1)); + const errno = @intFromEnum(std.c.getErrno(-1)); if (errno != 0) { err.put(globalObject, ZigString.static("errno"), JSValue.jsNumber(errno)); if (bun.C.SystemErrno.init(errno)) |str| { @@ -496,6 +522,10 @@ pub const Listener = struct { }; if (ssl_enabled) { + if (ssl.?.protos) |p| { + protos = p[0..ssl.?.protos_len]; + } + uws.NewSocketHandler(true).configure( socket_context, true, @@ -544,7 +574,7 @@ pub const Listener = struct { defer bun.default_allocator.free(host); const socket = uws.us_socket_context_listen( - @boolToInt(ssl_enabled), + @intFromBool(ssl_enabled), socket_context, normalizeHost(@as([:0]const u8, host)), c.port, @@ -560,13 +590,13 @@ pub const Listener = struct { .unix => |u| { var host = bun.default_allocator.dupeZ(u8, u) catch unreachable; defer bun.default_allocator.free(host); - break :brk uws.us_socket_context_listen_unix(@boolToInt(ssl_enabled), socket_context, host, socket_flags, 8); + break :brk uws.us_socket_context_listen_unix(@intFromBool(ssl_enabled), socket_context, host, socket_flags, 8); }, } } orelse { defer { hostname_or_unix.deinit(); - uws.us_socket_context_free(@boolToInt(ssl_enabled), socket_context); + uws.us_socket_context_free(@intFromBool(ssl_enabled), socket_context); } const err = globalObject.createErrorInstance( @@ -575,7 +605,7 @@ pub const Listener = struct { bun.span(hostname_or_unix.slice()), }, ); - const errno = @enumToInt(std.c.getErrno(-1)); + const errno = @intFromEnum(std.c.getErrno(-1)); if (errno != 0) { err.put(globalObject, ZigString.static("errno"), JSValue.jsNumber(errno)); if (bun.C.SystemErrno.init(errno)) |str| { @@ -593,6 +623,7 @@ pub const Listener = struct { .ssl = ssl_enabled, .socket_context = socket_context, .listener = listen_socket, + .protos = if (protos) |p| (bun.default_allocator.dupe(u8, p) catch unreachable) else null, }; socket.handlers.protect(); @@ -649,6 +680,8 @@ pub const Listener = struct { .handlers = &listener.handlers, .this_value = .zero, .socket = socket, + .protos = listener.protos, + .owned_protos = false, }; if (listener.strong_data.get()) |default_data| { const globalObject = listener.handlers.globalObject; @@ -715,6 +748,10 @@ pub const Listener = struct { this.handlers.unprotect(); this.connection.deinit(); + if (this.protos) |protos| { + this.protos = null; + bun.default_allocator.destroy(protos); + } bun.default_allocator.destroy(this); } @@ -775,13 +812,17 @@ pub const Listener = struct { const socket_config = SocketConfig.fromJS(opts, globalObject, exception) orelse { return .zero; }; + var hostname_or_unix = socket_config.hostname_or_unix; var port = socket_config.port; var ssl = socket_config.ssl; var handlers = socket_config.handlers; var default_data = socket_config.default_data; + var protos: ?[]const u8 = null; + var server_name: ?[]const u8 = null; const ssl_enabled = ssl != null; + defer if (ssl != null) ssl.?.deinit(); handlers.protect(); @@ -789,7 +830,7 @@ pub const Listener = struct { globalObject.bunVM().eventLoop().ensureWaker(); - var socket_context = uws.us_create_bun_socket_context(@boolToInt(ssl_enabled), uws.Loop.get().?, @sizeOf(usize), ctx_opts).?; + var socket_context = uws.us_create_bun_socket_context(@intFromBool(ssl_enabled), uws.Loop.get().?, @sizeOf(usize), ctx_opts).?; var connection: Listener.UnixOrHost = if (port) |port_| .{ .host = .{ .host = (hostname_or_unix.cloneIfNeeded(bun.default_allocator) catch unreachable).slice(), .port = port_ }, } else .{ @@ -797,6 +838,12 @@ pub const Listener = struct { }; if (ssl_enabled) { + if (ssl.?.protos) |p| { + protos = p[0..ssl.?.protos_len]; + } + if (ssl.?.server_name) |s| { + server_name = bun.default_allocator.dupe(u8, s[0..bun.len(s)]) catch unreachable; + } uws.NewSocketHandler(true).configure( socket_context, true, @@ -848,6 +895,8 @@ pub const Listener = struct { .this_value = .zero, .socket = undefined, .connection = connection, + .protos = if (protos) |p| (bun.default_allocator.dupe(u8, p) catch unreachable) else null, + .server_name = server_name, }; TLSSocket.dataSetCached(tls.getThisValue(globalObject), globalObject, default_data); @@ -871,6 +920,8 @@ pub const Listener = struct { .this_value = .zero, .socket = undefined, .connection = null, + .protos = null, + .server_name = null, }; TCPSocket.dataSetCached(tcp.getThisValue(globalObject), globalObject, default_data); @@ -898,11 +949,41 @@ fn JSSocketType(comptime ssl: bool) type { } } +fn selectALPNCallback( + _: ?*BoringSSL.SSL, + out: [*c][*c]const u8, + outlen: [*c]u8, + in: [*c]const u8, + inlen: c_uint, + arg: ?*anyopaque, +) callconv(.C) c_int { + const this = bun.cast(*TLSSocket, arg); + if (this.protos) |protos| { + if (protos.len == 0) { + return BoringSSL.SSL_TLSEXT_ERR_NOACK; + } + + const status = BoringSSL.SSL_select_next_proto(bun.cast([*c][*c]u8, out), outlen, protos.ptr, @intCast(c_uint, protos.len), in, inlen); + + // Previous versions of Node.js returned SSL_TLSEXT_ERR_NOACK if no protocol + // match was found. This would neither cause a fatal alert nor would it result + // in a useful ALPN response as part of the Server Hello message. + // We now return SSL_TLSEXT_ERR_ALERT_FATAL in that case as per Section 3.2 + // of RFC 7301, which causes a fatal no_application_protocol alert. + const expected = if (comptime BoringSSL.OPENSSL_NPN_NEGOTIATED == 1) BoringSSL.SSL_TLSEXT_ERR_OK else BoringSSL.SSL_TLSEXT_ERR_ALERT_FATAL; + + return if (status == expected) 1 else 0; + } else { + return BoringSSL.SSL_TLSEXT_ERR_NOACK; + } +} + fn NewSocket(comptime ssl: bool) type { return struct { pub const Socket = uws.NewSocketHandler(ssl); socket: Socket, detached: bool = false, + wrapped: WrappedType = .none, handlers: *Handlers, this_value: JSC.JSValue = .zero, poll_ref: JSC.PollRef = JSC.PollRef.init(), @@ -910,6 +991,9 @@ fn NewSocket(comptime ssl: bool) type { last_4: [4]u8 = .{ 0, 0, 0, 0 }, authorized: bool = false, connection: ?Listener.UnixOrHost = null, + protos: ?[]const u8, + owned_protos: bool = true, + server_name: ?[]const u8 = null, // TODO: switch to something that uses `visitAggregate` and have the // `Listener` keep a list of all the sockets JSValue in there @@ -1022,8 +1106,8 @@ fn NewSocket(comptime ssl: bool) type { var globalObject = handlers.globalObject; const err = JSC.SystemError{ .errno = errno, - .message = ZigString.init("Failed to connect"), - .syscall = ZigString.init("connect"), + .message = bun.String.static("Failed to connect"), + .syscall = bun.String.static("connect"), }; if (callback == .zero) { @@ -1079,7 +1163,7 @@ fn NewSocket(comptime ssl: bool) type { var vm = this.handlers.vm; this.reffer.unref(vm); - this.handlers.markInactive(ssl, this.socket.context()); + this.handlers.markInactive(ssl, this.socket.context(), this.wrapped); this.poll_ref.unref(vm); this.has_pending_activity.store(false, .Release); } @@ -1091,25 +1175,42 @@ fn NewSocket(comptime ssl: bool) type { // Add SNI support for TLS (mongodb and others requires this) if (comptime ssl) { - if (this.connection) |connection| { - if (connection == .host) { - const host = normalizeHost(connection.host.host); + var ssl_ptr: *BoringSSL.SSL = @ptrCast(*BoringSSL.SSL, socket.getNativeHandle()); + if (!ssl_ptr.isInitFinished()) { + if (this.server_name) |server_name| { + const host = normalizeHost(server_name); if (host.len > 0) { - var ssl_ptr: *BoringSSL.SSL = @ptrCast(*BoringSSL.SSL, socket.getNativeHandle()); - if (!ssl_ptr.isInitFinished()) { + var host__ = default_allocator.dupeZ(u8, host) catch unreachable; + defer default_allocator.free(host__); + ssl_ptr.setHostname(host__); + } + } else if (this.connection) |connection| { + if (connection == .host) { + const host = normalizeHost(connection.host.host); + if (host.len > 0) { var host__ = default_allocator.dupeZ(u8, host) catch unreachable; defer default_allocator.free(host__); ssl_ptr.setHostname(host__); } } } + if (this.protos) |protos| { + if (this.handlers.is_server) { + BoringSSL.SSL_CTX_set_alpn_select_cb(BoringSSL.SSL_get_SSL_CTX(ssl_ptr), selectALPNCallback, bun.cast(*anyopaque, this)); + } else { + _ = BoringSSL.SSL_set_alpn_protos(ssl_ptr, protos.ptr, @intCast(c_uint, protos.len)); + } + } } } this.poll_ref.ref(this.handlers.vm); this.detached = false; this.socket = socket; - socket.ext(**anyopaque).?.* = bun.cast(**anyopaque, this); + + if (this.wrapped == .none) { + socket.ext(**anyopaque).?.* = bun.cast(**anyopaque, this); + } const handlers = this.handlers; const callback = handlers.onOpen; @@ -1161,6 +1262,8 @@ fn NewSocket(comptime ssl: bool) type { pub fn onEnd(this: *This, socket: Socket) void { JSC.markBinding(@src()); log("onEnd", .{}); + if (this.detached) return; + this.detached = true; defer this.markInactive(); @@ -1174,7 +1277,7 @@ fn NewSocket(comptime ssl: bool) type { // the handlers must be kept alive for the duration of the function call // that way if we need to call the error handler, we can var scope = handlers.enter(socket.context()); - defer scope.exit(ssl); + defer scope.exit(ssl, this.wrapped); const globalObject = handlers.globalObject; const this_value = this.getThisValue(globalObject); @@ -1211,7 +1314,7 @@ fn NewSocket(comptime ssl: bool) type { // the handlers must be kept alive for the duration of the function call // that way if we need to call the error handler, we can var scope = handlers.enter(socket.context()); - defer scope.exit(ssl); + defer scope.exit(ssl, this.wrapped); const globalObject = handlers.globalObject; const this_value = this.getThisValue(globalObject); @@ -1232,8 +1335,8 @@ fn NewSocket(comptime ssl: bool) type { const reason = if (ssl_error.reason == null) "" else ssl_error.reason[0..bun.len(ssl_error.reason)]; const fallback = JSC.SystemError{ - .code = ZigString.init(code), - .message = ZigString.init(reason), + .code = bun.String.create(code), + .message = bun.String.create(reason), }; authorization_error = fallback.toErrorInstance(globalObject); @@ -1255,7 +1358,6 @@ fn NewSocket(comptime ssl: bool) type { log("onClose", .{}); this.detached = true; defer this.markInactive(); - const handlers = this.handlers; this.poll_ref.unref(handlers.vm); @@ -1265,7 +1367,7 @@ fn NewSocket(comptime ssl: bool) type { // the handlers must be kept alive for the duration of the function call // that way if we need to call the error handler, we can var scope = handlers.enter(socket.context()); - defer scope.exit(ssl); + defer scope.exit(ssl, this.wrapped); var globalObject = handlers.globalObject; const this_value = this.getThisValue(globalObject); @@ -1295,7 +1397,7 @@ fn NewSocket(comptime ssl: bool) type { // the handlers must be kept alive for the duration of the function call // that way if we need to call the error handler, we can var scope = handlers.enter(socket.context()); - defer scope.exit(ssl); + defer scope.exit(ssl, this.wrapped); // const encoding = handlers.encoding; const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ @@ -1409,8 +1511,8 @@ fn NewSocket(comptime ssl: bool) type { const reason = if (ssl_error.reason == null) "" else ssl_error.reason[0..bun.len(ssl_error.reason)]; const fallback = JSC.SystemError{ - .code = ZigString.init(code), - .message = ZigString.init(reason), + .code = bun.String.create(code), + .message = bun.String.create(reason), }; return fallback.toErrorInstance(globalObject); @@ -1476,10 +1578,20 @@ fn NewSocket(comptime ssl: bool) type { } fn writeMaybeCorked(this: *This, buffer: []const u8, is_end: bool) i32 { - if (this.socket.isShutdown() or this.socket.isClosed()) { + if (this.detached or this.socket.isShutdown() or this.socket.isClosed()) { return -1; } // we don't cork yet but we might later + + if (comptime ssl) { + // TLS wrapped but in TCP mode + if (this.wrapped == .tcp) { + const res = this.socket.rawWrite(buffer, is_end); + log("write({d}, {any}) = {d}", .{ buffer.len, is_end, res }); + return res; + } + } + const res = this.socket.write(buffer, is_end); log("write({d}, {any}) = {d}", .{ buffer.len, is_end, res }); return res; @@ -1487,7 +1599,6 @@ fn NewSocket(comptime ssl: bool) type { fn writeOrEnd(this: *This, globalObject: *JSC.JSGlobalObject, args: []const JSC.JSValue, is_end: bool) WriteResult { if (args.len == 0) return .{ .success = .{} }; - if (args.ptr[0].asArrayBuffer(globalObject)) |array_buffer| { var slice = array_buffer.slice(); @@ -1681,9 +1792,6 @@ fn NewSocket(comptime ssl: bool) type { if (result.wrote == result.total) { this.socket.flush(); this.detached = true; - if (!this.socket.isClosed()) { - this.socket.close(0, null); - } this.markInactive(); } break :brk JSValue.jsNumber(result.wrote); @@ -1706,17 +1814,32 @@ fn NewSocket(comptime ssl: bool) type { pub fn finalize(this: *This) callconv(.C) void { log("finalize()", .{}); - if (this.detached) return; - this.detached = true; - if (!this.socket.isClosed()) { - this.socket.close(0, null); + if (!this.detached) { + this.detached = true; + if (!this.socket.isClosed()) { + this.socket.close(0, null); + } + this.markInactive(); } + + this.poll_ref.unref(JSC.VirtualMachine.get()); + // need to deinit event without being attached + if (this.owned_protos) { + if (this.protos) |protos| { + this.protos = null; + default_allocator.free(protos); + } + } + + if (this.server_name) |server_name| { + this.server_name = null; + default_allocator.free(server_name); + } + if (this.connection) |connection| { - connection.deinit(); this.connection = null; + connection.deinit(); } - this.markInactive(); - this.poll_ref.unref(JSC.VirtualMachine.get()); } pub fn reload(this: *This, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { @@ -1756,8 +1879,384 @@ fn NewSocket(comptime ssl: bool) type { return JSValue.jsUndefined(); } + + pub fn getALPNProtocol( + this: *This, + globalObject: *JSC.JSGlobalObject, + ) callconv(.C) JSValue { + if (comptime ssl == false) { + return JSValue.jsBoolean(false); + } + + if (this.detached) { + return JSValue.jsBoolean(false); + } + + var alpn_proto: [*c]const u8 = null; + var alpn_proto_len: u32 = 0; + + var ssl_ptr: *BoringSSL.SSL = @ptrCast(*BoringSSL.SSL, this.socket.getNativeHandle()); + BoringSSL.SSL_get0_alpn_selected(ssl_ptr, &alpn_proto, &alpn_proto_len); + if (alpn_proto == null or alpn_proto_len == 0) { + return JSValue.jsBoolean(false); + } + + const slice = alpn_proto[0..alpn_proto_len]; + if (strings.eql(slice, "h2")) { + return ZigString.static("h2").toValue(globalObject); + } + if (strings.eql(slice, "http/1.1")) { + return ZigString.static("http/1.1").toValue(globalObject); + } + return ZigString.fromUTF8(slice).toValueGC(globalObject); + } + + pub fn setServername( + this: *This, + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSValue { + if (comptime ssl == false) { + return JSValue.jsUndefined(); + } + + if (this.handlers.is_server) { + globalObject.throw("Cannot issue SNI from a TLS server-side socket", .{}); + return .zero; + } + + const args = callframe.arguments(1); + if (args.len < 1) { + globalObject.throw("Expected 1 argument", .{}); + return .zero; + } + + const server_name = args.ptr[0]; + if (!server_name.isString()) { + globalObject.throw("Expected \"serverName\" to be a string", .{}); + return .zero; + } + + const slice = server_name.getZigString(globalObject).toOwnedSlice(bun.default_allocator) catch unreachable; + if (this.server_name) |old| { + this.server_name = slice; + default_allocator.free(old); + } else { + this.server_name = slice; + } + + if (this.detached) { + // will be attached onOpen + return JSValue.jsUndefined(); + } + + const host = normalizeHost(@as([]const u8, slice)); + if (host.len > 0) { + var ssl_ptr: *BoringSSL.SSL = @ptrCast(*BoringSSL.SSL, this.socket.getNativeHandle()); + if (ssl_ptr.isInitFinished()) { + // match node.js exceptions + globalObject.throw("Already started.", .{}); + return .zero; + } + var host__ = default_allocator.dupeZ(u8, host) catch unreachable; + defer default_allocator.free(host__); + ssl_ptr.setHostname(host__); + } + + return JSValue.jsUndefined(); + } + + // this invalidates the current socket returning 2 new sockets + // one for non-TLS and another for TLS + // handlers for non-TLS are preserved + pub fn upgradeTLS( + this: *This, + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSValue { + JSC.markBinding(@src()); + if (comptime ssl) { + return JSValue.jsUndefined(); + } + + if (this.detached) { + return JSValue.jsUndefined(); + } + + const args = callframe.arguments(1); + + if (args.len < 1) { + globalObject.throw("Expected 1 arguments", .{}); + return .zero; + } + + var exception: JSC.C.JSValueRef = null; + + const opts = args.ptr[0]; + if (opts.isEmptyOrUndefinedOrNull() or opts.isBoolean() or !opts.isObject()) { + globalObject.throw("Expected options object", .{}); + return .zero; + } + + var socket_obj = opts.get(globalObject, "socket") orelse { + globalObject.throw("Expected \"socket\" option", .{}); + return .zero; + }; + + var handlers = Handlers.fromJS(globalObject, socket_obj, &exception) orelse { + globalObject.throwValue(exception.?.value()); + return .zero; + }; + + var ssl_opts: ?JSC.API.ServerConfig.SSLConfig = null; + + if (opts.getTruthy(globalObject, "tls")) |tls| { + if (tls.isBoolean()) { + if (tls.toBoolean()) { + ssl_opts = JSC.API.ServerConfig.SSLConfig.zero; + } + } else { + if (JSC.API.ServerConfig.SSLConfig.inJS(globalObject, tls, &exception)) |ssl_config| { + ssl_opts = ssl_config; + } else if (exception != null) { + return .zero; + } + } + } + + if (ssl_opts == null) { + globalObject.throw("Expected \"tls\" option", .{}); + return .zero; + } + + var default_data = JSValue.zero; + if (opts.getTruthy(globalObject, "data")) |default_data_value| { + default_data = default_data_value; + default_data.ensureStillAlive(); + } + + var socket_config = ssl_opts.?; + defer socket_config.deinit(); + const options = socket_config.asUSockets(); + + const protos = socket_config.protos; + const protos_len = socket_config.protos_len; + + const ext_size = @sizeOf(WrappedSocket); + + const is_server = this.handlers.is_server; + var tls = handlers.vm.allocator.create(TLSSocket) catch @panic("OOM"); + var handlers_ptr = handlers.vm.allocator.create(Handlers) catch @panic("OOM"); + handlers_ptr.* = handlers; + handlers_ptr.is_server = is_server; + handlers_ptr.protect(); + + tls.* = .{ + .handlers = handlers_ptr, + .this_value = .zero, + .socket = undefined, + .connection = if (this.connection) |c| c.clone() else null, + .wrapped = .tls, + .protos = if (protos) |p| (bun.default_allocator.dupe(u8, p[0..protos_len]) catch unreachable) else null, + .server_name = if (socket_config.server_name) |server_name| (bun.default_allocator.dupe(u8, server_name[0..bun.len(server_name)]) catch unreachable) else null, + }; + + var tls_js_value = tls.getThisValue(globalObject); + TLSSocket.dataSetCached(tls_js_value, globalObject, default_data); + + const TCPHandler = NewWrappedHandler(false); + + // reconfigure context to use the new wrapper handlers + Socket.unsafeConfigure(this.socket.context(), true, true, WrappedSocket, TCPHandler); + const old_context = this.socket.context(); + const TLSHandler = NewWrappedHandler(true); + const new_socket = this.socket.wrapTLS( + options, + ext_size, + true, + WrappedSocket, + TLSHandler, + ) orelse { + handlers_ptr.unprotect(); + handlers.vm.allocator.destroy(handlers_ptr); + bun.default_allocator.destroy(tls); + return JSValue.jsUndefined(); + }; + + tls.socket = new_socket; + + var raw = handlers.vm.allocator.create(TLSSocket) catch @panic("OOM"); + var raw_handlers_ptr = handlers.vm.allocator.create(Handlers) catch @panic("OOM"); + raw_handlers_ptr.* = .{ + .vm = globalObject.bunVM(), + .globalObject = globalObject, + .onOpen = this.handlers.onOpen, + .onClose = this.handlers.onClose, + .onData = this.handlers.onData, + .onWritable = this.handlers.onWritable, + .onTimeout = this.handlers.onTimeout, + .onConnectError = this.handlers.onConnectError, + .onEnd = this.handlers.onEnd, + .onError = this.handlers.onError, + .onHandshake = this.handlers.onHandshake, + .binary_type = this.handlers.binary_type, + .is_server = is_server, + }; + this.handlers.onOpen = .zero; + this.handlers.onClose = .zero; + this.handlers.onData = .zero; + this.handlers.onWritable = .zero; + this.handlers.onTimeout = .zero; + this.handlers.onConnectError = .zero; + this.handlers.onEnd = .zero; + this.handlers.onError = .zero; + this.handlers.onHandshake = .zero; + raw.* = .{ + .handlers = raw_handlers_ptr, + .this_value = .zero, + .socket = new_socket, + .connection = if (this.connection) |c| c.clone() else null, + .wrapped = .tcp, + .protos = null, + }; + + var raw_js_value = raw.getThisValue(globalObject); + if (JSSocketType(ssl).dataGetCached(this.getThisValue(globalObject))) |raw_default_data| { + raw_default_data.ensureStillAlive(); + TLSSocket.dataSetCached(raw_js_value, globalObject, raw_default_data); + } + // marks both as active + raw.markActive(); + // this will keep tls alive until socket.open() is called to start TLS certificate and the handshake process + // open is not immediately called because we need to set bunSocketInternal + tls.markActive(); + + // mark both instances on socket data + new_socket.ext(WrappedSocket).?.* = .{ .tcp = raw, .tls = tls }; + + // start TLS handshake after we set ext + new_socket.startTLS(!this.handlers.is_server); + + //detach and invalidate the old instance + this.detached = true; + if (this.reffer.has) { + var vm = this.handlers.vm; + this.reffer.unref(vm); + old_context.deinit(ssl); + bun.default_allocator.destroy(this.handlers); + this.poll_ref.unref(vm); + this.has_pending_activity.store(false, .Release); + } + + const array = JSC.JSValue.createEmptyArray(globalObject, 2); + array.putIndex(globalObject, 0, raw_js_value); + array.putIndex(globalObject, 1, tls_js_value); + return array; + } }; } pub const TCPSocket = NewSocket(false); pub const TLSSocket = NewSocket(true); + +pub const WrappedSocket = extern struct { + // both shares the same socket but one behaves as TLS and the other as TCP + tls: *TLSSocket, + tcp: *TLSSocket, +}; + +pub fn NewWrappedHandler(comptime tls: bool) type { + const Socket = uws.NewSocketHandler(true); + return struct { + pub fn onOpen( + this: WrappedSocket, + socket: Socket, + ) void { + // only TLS will call onOpen + if (comptime tls) { + TLSSocket.onOpen(this.tls, socket); + } + } + + pub fn onEnd( + this: WrappedSocket, + socket: Socket, + ) void { + if (comptime tls) { + TLSSocket.onEnd(this.tls, socket); + } else { + TLSSocket.onEnd(this.tcp, socket); + } + } + + pub fn onHandshake( + this: WrappedSocket, + socket: Socket, + success: i32, + ssl_error: uws.us_bun_verify_error_t, + ) void { + // only TLS will call onHandshake + if (comptime tls) { + TLSSocket.onHandshake(this.tls, socket, success, ssl_error); + } + } + + pub fn onClose( + this: WrappedSocket, + socket: Socket, + err: c_int, + data: ?*anyopaque, + ) void { + if (comptime tls) { + TLSSocket.onClose(this.tls, socket, err, data); + } else { + TLSSocket.onClose(this.tcp, socket, err, data); + } + } + + pub fn onData( + this: WrappedSocket, + socket: Socket, + data: []const u8, + ) void { + if (comptime tls) { + TLSSocket.onData(this.tls, socket, data); + } else { + TLSSocket.onData(this.tcp, socket, data); + } + } + + pub fn onWritable( + this: WrappedSocket, + socket: Socket, + ) void { + if (comptime tls) { + TLSSocket.onWritable(this.tls, socket); + } else { + TLSSocket.onWritable(this.tcp, socket); + } + } + pub fn onTimeout( + this: WrappedSocket, + socket: Socket, + ) void { + if (comptime tls) { + TLSSocket.onTimeout(this.tls, socket); + } else { + TLSSocket.onTimeout(this.tcp, socket); + } + } + + pub fn onConnectError( + this: WrappedSocket, + socket: Socket, + errno: c_int, + ) void { + if (comptime tls) { + TLSSocket.onConnectError(this.tls, socket, errno); + } else { + TLSSocket.onConnectError(this.tcp, socket, errno); + } + } + }; +} diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index 832afac78..ba813c463 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -1011,7 +1011,7 @@ pub const Subprocess = struct { if (signal.name()) |name| return JSC.ZigString.init(name).toValueGC(global) else - return JSC.JSValue.jsNumber(@enumToInt(signal)); + return JSC.JSValue.jsNumber(@intFromEnum(signal)); } return JSC.JSValue.jsNull(); @@ -1535,9 +1535,9 @@ pub const Subprocess = struct { } if (std.os.W.IFSIGNALED(result.status)) { - this.signal_code = @intToEnum(SignalCode, @truncate(u8, std.os.W.TERMSIG(result.status))); + this.signal_code = @enumFromInt(SignalCode, @truncate(u8, std.os.W.TERMSIG(result.status))); } else if (std.os.W.IFSTOPPED(result.status)) { - this.signal_code = @intToEnum(SignalCode, @truncate(u8, std.os.W.STOPSIG(result.status))); + this.signal_code = @enumFromInt(SignalCode, @truncate(u8, std.os.W.STOPSIG(result.status))); } if (!this.hasExited()) { diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index fe2b50955..ba31b67ed 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -137,8 +137,8 @@ pub const FFI = struct { globalThis, ZigString.static("ptr"), ZigString.static("ctx"), - JSC.JSValue.fromPtrAddress(@ptrToInt(function_.step.compiled.ptr)), - JSC.JSValue.fromPtrAddress(@ptrToInt(function_)), + JSC.JSValue.fromPtrAddress(@intFromPtr(function_.step.compiled.ptr)), + JSC.JSValue.fromPtrAddress(@intFromPtr(function_)), ); }, } @@ -311,9 +311,9 @@ pub const FFI = struct { break :brk std.DynLib.open(backup_name) catch { // Then, if that fails, report an error. const system_error = JSC.SystemError{ - .code = ZigString.init(@tagName(JSC.Node.ErrorCode.ERR_DLOPEN_FAILED)), - .message = ZigString.init("Failed to open library. This is usually caused by a missing library or an invalid library path."), - .syscall = ZigString.init("dlopen"), + .code = bun.String.create(@tagName(JSC.Node.ErrorCode.ERR_DLOPEN_FAILED)), + .message = bun.String.create("Failed to open library. This is usually caused by a missing library or an invalid library path."), + .syscall = bun.String.create("dlopen"), }; return system_error.toErrorInstance(global); }; @@ -523,7 +523,7 @@ pub const FFI = struct { const int = val.to(i32); switch (int) { 0...ABIType.max => { - abi_types.appendAssumeCapacity(@intToEnum(ABIType, int)); + abi_types.appendAssumeCapacity(@enumFromInt(ABIType, int)); continue; }, else => { @@ -560,7 +560,7 @@ pub const FFI = struct { const int = ret_value.toInt32(); switch (int) { 0...ABIType.max => { - return_type = @intToEnum(ABIType, int); + return_type = @enumFromInt(ABIType, int); break :brk; }, else => { @@ -594,11 +594,11 @@ pub const FFI = struct { if (ptr.isNumber()) { const num = ptr.asPtrAddress(); if (num > 0) - function.symbol_from_dynamic_library = @intToPtr(*anyopaque, num); + function.symbol_from_dynamic_library = @ptrFromInt(*anyopaque, num); } else { const num = ptr.toUInt64NoTruncate(); if (num > 0) { - function.symbol_from_dynamic_library = @intToPtr(*anyopaque, num); + function.symbol_from_dynamic_library = @ptrFromInt(*anyopaque, num); } } } @@ -866,7 +866,7 @@ pub const FFI = struct { c: u8, byte_count: usize, ) callconv(.C) void { - @memset(dest, c, byte_count); + @memset(dest[0..byte_count], c); } noinline fn memcpy( @@ -874,7 +874,7 @@ pub const FFI = struct { noalias source: [*]const u8, byte_count: usize, ) callconv(.C) void { - @memcpy(dest, source, byte_count); + @memcpy(dest[0..byte_count], source[0..byte_count]); } pub fn define(state: *TCC.TCCState) void { @@ -1205,7 +1205,7 @@ pub const FFI = struct { writer: anytype, ) !void { { - const ptr = @ptrToInt(globalObject); + const ptr = @intFromPtr(globalObject); const fmt = bun.fmt.hexIntUpper(ptr); try writer.print("#define JS_GLOBAL_OBJECT (void*)0x{any}ULL\n", .{fmt}); } @@ -1290,7 +1290,7 @@ pub const FFI = struct { var inner_buf: []u8 = &.{}; { - const ptr = @ptrToInt(context_ptr); + const ptr = @intFromPtr(context_ptr); const fmt = bun.fmt.hexIntUpper(ptr); if (this.arg_types.items.len > 0) { @@ -1355,7 +1355,7 @@ pub const FFI = struct { function = 17, - pub const max = @enumToInt(ABIType.function); + pub const max = @intFromEnum(ABIType.function); /// Types that we can directly pass through as an `int64_t` pub fn needsACastInC(this: ABIType) bool { @@ -1414,11 +1414,11 @@ pub const FFI = struct { // these are not all valid identifiers try writer.writeAll(self.name); try writer.writeAll("']:"); - try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer); + try std.fmt.formatInt(@intFromEnum(self.entry), 10, .lower, .{}, writer); try writer.writeAll(",'"); - try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer); + try std.fmt.formatInt(@intFromEnum(self.entry), 10, .lower, .{}, writer); try writer.writeAll("':"); - try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer); + try std.fmt.formatInt(@intFromEnum(self.entry), 10, .lower, .{}, writer); } }; pub const map_to_js_object = brk: { @@ -1426,7 +1426,7 @@ pub const FFI = struct { for (map, 0..) |item, i| { var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" }; count += std.fmt.count("{}", .{fmt}); - count += @boolToInt(i > 0); + count += @intFromBool(i > 0); } var buf: [count]u8 = undefined; diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig index bfbdb9a37..b309e07d7 100644 --- a/src/bun.js/api/html_rewriter.zig +++ b/src/bun.js/api/html_rewriter.zig @@ -106,7 +106,7 @@ pub const HTMLRewriter = struct { var selector = LOLHTML.HTMLSelector.parse(selector_slice) catch return throwLOLHTMLError(global); - var handler_ = ElementHandler.init(global, listener, exception); + var handler_ = ElementHandler.init(global, listener, exception) catch return .zero; if (exception.* != null) { selector.deinit(); return JSValue.fromRef(exception.*); @@ -154,7 +154,7 @@ pub const HTMLRewriter = struct { thisObject: JSC.C.JSObjectRef, exception: JSC.C.ExceptionRef, ) JSValue { - var handler_ = DocumentHandler.init(global, listener, exception); + var handler_ = DocumentHandler.init(global, listener, exception) catch return .zero; if (exception.* != null) { return JSValue.fromRef(exception.*); } @@ -446,10 +446,14 @@ pub const HTMLRewriter = struct { }, }; - result.body.init.headers = original.body.init.headers; result.body.init.method = original.body.init.method; result.body.init.status_code = original.body.init.status_code; + // https://github.com/oven-sh/bun/issues/3334 + if (original.body.init.headers) |headers| { + result.body.init.headers = headers.cloneThis(global); + } + result.url = bun.default_allocator.dupe(u8, original.url) catch unreachable; result.status_text = bun.default_allocator.dupe(u8, original.status_text) catch unreachable; @@ -472,13 +476,13 @@ pub const HTMLRewriter = struct { pub fn onFinishedLoading(sink: *BufferOutputSink, bytes: JSC.WebCore.Blob.Store.ReadFile.ResultType) void { switch (bytes) { .err => |err| { - if (sink.response.body.value == .Locked and @ptrToInt(sink.response.body.value.Locked.task) == @ptrToInt(sink) and + if (sink.response.body.value == .Locked and @intFromPtr(sink.response.body.value.Locked.task) == @intFromPtr(sink) and sink.response.body.value.Locked.promise == null) { sink.response.body.value = .{ .Empty = {} }; // is there a pending promise? // we will need to reject it - } else if (sink.response.body.value == .Locked and @ptrToInt(sink.response.body.value.Locked.task) == @ptrToInt(sink) and + } else if (sink.response.body.value == .Locked and @intFromPtr(sink.response.body.value.Locked.task) == @intFromPtr(sink) and sink.response.body.value.Locked.promise != null) { sink.response.body.value.Locked.onReceiveValue = null; @@ -723,29 +727,44 @@ const DocumentHandler = struct { "onEndCallback", ); - pub fn init(global: *JSGlobalObject, thisObject: JSValue, exception: JSC.C.ExceptionRef) DocumentHandler { + pub fn init(global: *JSGlobalObject, thisObject: JSValue, exception: JSC.C.ExceptionRef) !DocumentHandler { var handler = DocumentHandler{ .thisObject = thisObject, .global = global, }; - switch (thisObject.jsType()) { - .Object, .ProxyObject, .Cell, .FinalObject => {}, - else => |kind| { - JSC.throwInvalidArguments( - "Expected object but received {s}", - .{@as(string, @tagName(kind))}, - global, - exception, - ); - return undefined; - }, + if (!thisObject.isObject()) { + JSC.throwInvalidArguments( + "Expected object", + .{}, + global, + exception, + ); + return error.InvalidArguments; + } + + errdefer { + if (handler.onDocTypeCallback) |cb| { + cb.unprotect(); + } + + if (handler.onCommentCallback) |cb| { + cb.unprotect(); + } + + if (handler.onTextCallback) |cb| { + cb.unprotect(); + } + + if (handler.onEndCallback) |cb| { + cb.unprotect(); + } } if (thisObject.get(global, "doctype")) |val| { if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { JSC.throwInvalidArguments("doctype must be a function", .{}, global, exception); - return undefined; + return error.InvalidArguments; } JSC.C.JSValueProtect(global, val.asObjectRef()); handler.onDocTypeCallback = val; @@ -754,7 +773,7 @@ const DocumentHandler = struct { if (thisObject.get(global, "comments")) |val| { if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { JSC.throwInvalidArguments("comments must be a function", .{}, global, exception); - return undefined; + return error.InvalidArguments; } JSC.C.JSValueProtect(global, val.asObjectRef()); handler.onCommentCallback = val; @@ -763,7 +782,7 @@ const DocumentHandler = struct { if (thisObject.get(global, "text")) |val| { if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { JSC.throwInvalidArguments("text must be a function", .{}, global, exception); - return undefined; + return error.InvalidArguments; } JSC.C.JSValueProtect(global, val.asObjectRef()); handler.onTextCallback = val; @@ -772,7 +791,7 @@ const DocumentHandler = struct { if (thisObject.get(global, "end")) |val| { if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { JSC.throwInvalidArguments("end must be a function", .{}, global, exception); - return undefined; + return error.InvalidArguments; } JSC.C.JSValueProtect(global, val.asObjectRef()); handler.onEndCallback = val; @@ -863,29 +882,39 @@ const ElementHandler = struct { global: *JSGlobalObject, ctx: ?*HTMLRewriter.BufferOutputSink = null, - pub fn init(global: *JSGlobalObject, thisObject: JSValue, exception: JSC.C.ExceptionRef) ElementHandler { + pub fn init(global: *JSGlobalObject, thisObject: JSValue, exception: JSC.C.ExceptionRef) !ElementHandler { var handler = ElementHandler{ .thisObject = thisObject, .global = global, }; + errdefer { + if (handler.onCommentCallback) |cb| { + cb.unprotect(); + } - switch (thisObject.jsType()) { - .Object, .ProxyObject, .Cell, .FinalObject => {}, - else => |kind| { - JSC.throwInvalidArguments( - "Expected object but received {s}", - .{@as(string, @tagName(kind))}, - global, - exception, - ); - return undefined; - }, + if (handler.onElementCallback) |cb| { + cb.unprotect(); + } + + if (handler.onTextCallback) |cb| { + cb.unprotect(); + } + } + + if (!thisObject.isObject()) { + JSC.throwInvalidArguments( + "Expected object", + .{}, + global, + exception, + ); + return error.InvalidArguments; } if (thisObject.get(global, "element")) |val| { if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { JSC.throwInvalidArguments("element must be a function", .{}, global, exception); - return undefined; + return error.InvalidArguments; } JSC.C.JSValueProtect(global, val.asObjectRef()); handler.onElementCallback = val; @@ -894,7 +923,7 @@ const ElementHandler = struct { if (thisObject.get(global, "comments")) |val| { if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { JSC.throwInvalidArguments("comments must be a function", .{}, global, exception); - return undefined; + return error.InvalidArguments; } JSC.C.JSValueProtect(global, val.asObjectRef()); handler.onCommentCallback = val; @@ -903,7 +932,7 @@ const ElementHandler = struct { if (thisObject.get(global, "text")) |val| { if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) { JSC.throwInvalidArguments("text must be a function", .{}, global, exception); - return undefined; + return error.InvalidArguments; } JSC.C.JSValueProtect(global, val.asObjectRef()); handler.onTextCallback = val; @@ -967,26 +996,14 @@ const getterWrap = JSC.getterWrap; const setterWrap = JSC.setterWrap; const wrap = JSC.wrapSync; -pub fn free_html_writer_string(_: ?*anyopaque, ptr: ?*anyopaque, len: usize) callconv(.C) void { - var str = LOLHTML.HTMLString{ .ptr = bun.cast([*]const u8, ptr.?), .len = len }; - str.deinit(); -} - fn throwLOLHTMLError(global: *JSGlobalObject) JSValue { - var err = LOLHTML.HTMLString.lastError(); - return ZigString.init(err.slice()).toErrorInstance(global); + const err = LOLHTML.HTMLString.lastError(); + defer err.deinit(); + return ZigString.fromUTF8(err.slice()).toErrorInstance(global); } fn htmlStringValue(input: LOLHTML.HTMLString, globalObject: *JSGlobalObject) JSValue { - var str = ZigString.init( - input.slice(), - ); - str.detectEncoding(); - - return str.toExternalValueWithCallback( - globalObject, - free_html_writer_string, - ); + return input.toJS(globalObject); } pub const TextChunk = struct { @@ -1016,6 +1033,9 @@ pub const TextChunk = struct { .removed = .{ .get = getterWrap(TextChunk, "removed"), }, + .lastInTextNode = .{ + .get = getterWrap(TextChunk, "lastInTextNode"), + }, .text = .{ .get = getterWrap(TextChunk, "getText"), }, @@ -1084,6 +1104,10 @@ pub const TextChunk = struct { return JSC.JSValue.jsBoolean(this.text_chunk.?.isRemoved()); } + pub fn lastInTextNode(this: *TextChunk, _: *JSGlobalObject) JSValue { + return JSC.JSValue.jsBoolean(this.text_chunk.?.isLastInTextNode()); + } + pub fn finalize(this: *TextChunk) void { this.text_chunk = null; bun.default_allocator.destroy(this); @@ -1292,7 +1316,7 @@ pub const Comment = struct { pub fn getText(this: *Comment, global: *JSGlobalObject) JSValue { if (this.comment == null) return JSValue.jsNull(); - return ZigString.init(this.comment.?.getText().slice()).withEncoding().toValueGC(global); + return this.comment.?.getText().toJS(global); } pub fn setText( @@ -1422,7 +1446,7 @@ pub const EndTag = struct { if (this.end_tag == null) return JSC.JSValue.jsUndefined(); - return ZigString.init(this.end_tag.?.getName().slice()).withEncoding().toValueGC(global); + return this.end_tag.?.getName().toJS(global); } pub fn setName( @@ -1534,27 +1558,16 @@ pub const AttributeIterator = struct { return JSC.JSValue.jsNull(); }; - // TODO: don't clone here const value = attribute.value(); const name = attribute.name(); - defer name.deinit(); - defer value.deinit(); - var strs = [2]ZigString{ - ZigString.init(name.slice()), - ZigString.init(value.slice()), - }; - - var valid_strs: []ZigString = strs[0..2]; - - var array = JSC.JSValue.createStringArray( + return bun.String.toJSArray( globalObject, - valid_strs.ptr, - valid_strs.len, - true, + &[_]bun.String{ + name.toString(), + value.toString(), + }, ); - - return array; } }; pub const Element = struct { @@ -1660,19 +1673,12 @@ pub const Element = struct { var slice = name.toSlice(bun.default_allocator); defer slice.deinit(); - var attr = this.element.?.getAttribute(slice.slice()).slice(); + var attr = this.element.?.getAttribute(slice.slice()); if (attr.len == 0) return JSC.JSValue.jsNull(); - var str = ZigString.init( - attr, - ); - - return str.toExternalValueWithCallback( - globalObject, - free_html_writer_string, - ); + return attr.toJS(globalObject); } /// Returns a boolean indicating whether an attribute exists on the element. @@ -1847,8 +1853,9 @@ pub const Element = struct { pub fn getNamespaceURI(this: *Element, globalObject: *JSGlobalObject) JSValue { if (this.element == null) return JSValue.jsUndefined(); - - return ZigString.init(std.mem.span(this.element.?.namespaceURI())).toValueGC(globalObject); + var str = bun.String.create(std.mem.span(this.element.?.namespaceURI())); + defer str.deref(); + return str.toJS(globalObject); } pub fn getAttributes(this: *Element, globalObject: *JSGlobalObject) JSValue { diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 37bc601a5..9625ff693 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -163,12 +163,14 @@ pub const ServerConfig = struct { request_cert: i32 = 0, reject_unauthorized: i32 = 0, ssl_ciphers: [*c]const u8 = null, + protos: [*c]const u8 = null, + protos_len: usize = 0, const log = Output.scoped(.SSLConfig, false); pub fn asUSockets(this_: ?SSLConfig) uws.us_bun_socket_context_options_t { var ctx_opts: uws.us_bun_socket_context_options_t = undefined; - @memset(@ptrCast([*]u8, &ctx_opts), 0, @sizeOf(uws.us_bun_socket_context_options_t)); + @memset(@ptrCast([*]u8, &ctx_opts)[0..@sizeOf(uws.us_bun_socket_context_options_t)], 0); if (this_) |ssl_config| { if (ssl_config.key_file_name != null) @@ -181,7 +183,7 @@ pub const ServerConfig = struct { ctx_opts.dh_params_file_name = ssl_config.dh_params_file_name; if (ssl_config.passphrase != null) ctx_opts.passphrase = ssl_config.passphrase; - ctx_opts.ssl_prefer_low_memory_usage = @boolToInt(ssl_config.low_memory_mode); + ctx_opts.ssl_prefer_low_memory_usage = @intFromBool(ssl_config.low_memory_mode); if (ssl_config.key) |key| { ctx_opts.key = key.ptr; @@ -215,6 +217,7 @@ pub const ServerConfig = struct { "dh_params_file_name", "passphrase", "ssl_ciphers", + "protos", }; inline for (fields) |field| { @@ -270,6 +273,9 @@ pub const ServerConfig = struct { pub fn inJS(global: *JSC.JSGlobalObject, obj: JSC.JSValue, exception: JSC.C.ExceptionRef) ?SSLConfig { var result = zero; + var arena: @import("root").bun.ArenaAllocator = @import("root").bun.ArenaAllocator.init(bun.default_allocator); + defer arena.deinit(); + if (!obj.isObject()) { JSC.throwInvalidArguments("tls option expects an object", .{}, global, exception); return null; @@ -301,7 +307,6 @@ pub const ServerConfig = struct { var i: u32 = 0; var valid_count: u32 = 0; - var arena: @import("root").bun.ArenaAllocator = @import("root").bun.ArenaAllocator.init(bun.default_allocator); while (i < count) : (i += 1) { const item = js_obj.getIndex(global, i); if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item, exception)) |sb| { @@ -317,7 +322,6 @@ pub const ServerConfig = struct { valid_count += 1; any = true; } else { - arena.deinit(); // mark and free all CA's result.cert = native_array; result.deinit(); @@ -325,7 +329,6 @@ pub const ServerConfig = struct { } } else { global.throwInvalidArguments("key argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{}); - arena.deinit(); // mark and free all keys result.key = native_array; result.deinit(); @@ -333,8 +336,6 @@ pub const ServerConfig = struct { } } - arena.deinit(); - if (valid_count == 0) { bun.default_allocator.free(native_array); } else { @@ -356,7 +357,6 @@ pub const ServerConfig = struct { } } else { const native_array = bun.default_allocator.alloc([*c]const u8, 1) catch unreachable; - var arena: @import("root").bun.ArenaAllocator = @import("root").bun.ArenaAllocator.init(bun.default_allocator); if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj, exception)) |sb| { const sliced = sb.slice(); if (sliced.len > 0) { @@ -369,14 +369,11 @@ pub const ServerConfig = struct { } } else { global.throwInvalidArguments("key argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{}); - arena.deinit(); // mark and free all certs result.key = native_array; result.deinit(); return null; } - - arena.deinit(); } } @@ -394,6 +391,22 @@ pub const ServerConfig = struct { } } + if (obj.getTruthy(global, "ALPNProtocols")) |protocols| { + if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), protocols, exception)) |sb| { + const sliced = sb.slice(); + if (sliced.len > 0) { + result.protos = bun.default_allocator.dupeZ(u8, sliced) catch unreachable; + result.protos_len = sliced.len; + } + + any = true; + } else { + global.throwInvalidArguments("ALPNProtocols argument must be an string, Buffer or TypedArray", .{}); + result.deinit(); + return null; + } + } + if (obj.getTruthy(global, "cert")) |js_obj| { if (js_obj.jsType().isArray()) { const count = js_obj.getLength(global); @@ -403,7 +416,6 @@ pub const ServerConfig = struct { var i: u32 = 0; var valid_count: u32 = 0; - var arena: @import("root").bun.ArenaAllocator = @import("root").bun.ArenaAllocator.init(bun.default_allocator); while (i < count) : (i += 1) { const item = js_obj.getIndex(global, i); if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item, exception)) |sb| { @@ -419,7 +431,6 @@ pub const ServerConfig = struct { valid_count += 1; any = true; } else { - arena.deinit(); // mark and free all CA's result.cert = native_array; result.deinit(); @@ -427,7 +438,6 @@ pub const ServerConfig = struct { } } else { global.throwInvalidArguments("cert argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{}); - arena.deinit(); // mark and free all certs result.cert = native_array; result.deinit(); @@ -435,8 +445,6 @@ pub const ServerConfig = struct { } } - arena.deinit(); - if (valid_count == 0) { bun.default_allocator.free(native_array); } else { @@ -458,7 +466,6 @@ pub const ServerConfig = struct { } } else { const native_array = bun.default_allocator.alloc([*c]const u8, 1) catch unreachable; - var arena: @import("root").bun.ArenaAllocator = @import("root").bun.ArenaAllocator.init(bun.default_allocator); if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj, exception)) |sb| { const sliced = sb.slice(); if (sliced.len > 0) { @@ -471,14 +478,11 @@ pub const ServerConfig = struct { } } else { global.throwInvalidArguments("cert argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{}); - arena.deinit(); // mark and free all certs result.cert = native_array; result.deinit(); return null; } - - arena.deinit(); } } @@ -518,7 +522,6 @@ pub const ServerConfig = struct { var i: u32 = 0; var valid_count: u32 = 0; - var arena: @import("root").bun.ArenaAllocator = @import("root").bun.ArenaAllocator.init(bun.default_allocator); while (i < count) : (i += 1) { const item = js_obj.getIndex(global, i); if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item, exception)) |sb| { @@ -534,7 +537,6 @@ pub const ServerConfig = struct { valid_count += 1; any = true; } else { - arena.deinit(); // mark and free all CA's result.cert = native_array; result.deinit(); @@ -542,7 +544,6 @@ pub const ServerConfig = struct { } } else { global.throwInvalidArguments("ca argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{}); - arena.deinit(); // mark and free all CA's result.cert = native_array; result.deinit(); @@ -550,8 +551,6 @@ pub const ServerConfig = struct { } } - arena.deinit(); - if (valid_count == 0) { bun.default_allocator.free(native_array); } else { @@ -573,7 +572,6 @@ pub const ServerConfig = struct { } } else { const native_array = bun.default_allocator.alloc([*c]const u8, 1) catch unreachable; - var arena: @import("root").bun.ArenaAllocator = @import("root").bun.ArenaAllocator.init(bun.default_allocator); if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj, exception)) |sb| { const sliced = sb.slice(); if (sliced.len > 0) { @@ -586,13 +584,11 @@ pub const ServerConfig = struct { } } else { JSC.throwInvalidArguments("ca argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{}, global, exception); - arena.deinit(); // mark and free all certs result.ca = native_array; result.deinit(); return null; } - arena.deinit(); } } @@ -1000,6 +996,30 @@ const HTTPStatusText = struct { } }; +fn NewFlags(comptime debug_mode: bool) type { + return packed struct { + has_marked_complete: bool = false, + has_marked_pending: bool = false, + has_abort_handler: bool = false, + has_sendfile_ctx: bool = false, + has_called_error_handler: bool = false, + needs_content_length: bool = false, + needs_content_range: bool = false, + /// Used to avoid looking at the uws.Request struct after it's been freed + is_transfer_encoding: bool = false, + + /// Used to identify if request can be safely deinitialized + is_waiting_body: bool = false, + /// Used in renderMissing in debug mode to show the user an HTML page + /// Used to avoid looking at the uws.Request struct after it's been freed + is_web_browser_navigation: if (debug_mode) bool else void = if (debug_mode) false else {}, + has_written_status: bool = false, + response_protected: bool = false, + aborted: bool = false, + finalized: bun.DebugOnly(bool) = bun.DebugOnlyDefault(false), + }; +} + // This is defined separately partially to work-around an LLVM debugger bug. fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comptime ThisServer: type) type { return struct { @@ -1024,63 +1044,42 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp req: *uws.Request, signal: ?*JSC.WebCore.AbortSignal = null, method: HTTP.Method, - aborted: bool = false, - finalized: bun.DebugOnly(bool) = bun.DebugOnlyDefault(false), + + flags: NewFlags(debug_mode) = .{}, + upgrade_context: ?*uws.uws_socket_context_t = null, /// We can only safely free once the request body promise is finalized /// and the response is rejected + response_jsvalue: JSC.JSValue = JSC.JSValue.zero, pending_promises_for_abort: u8 = 0, - has_marked_complete: bool = false, - has_marked_pending: bool = false, - - response_jsvalue: JSC.JSValue = JSC.JSValue.zero, - response_protected: bool = false, response_ptr: ?*JSC.WebCore.Response = null, blob: JSC.WebCore.AnyBlob = JSC.WebCore.AnyBlob{ .Blob = .{} }, promise: ?*JSC.JSValue = null, - has_abort_handler: bool = false, - has_sendfile_ctx: bool = false, - has_called_error_handler: bool = false, - needs_content_length: bool = false, - needs_content_range: bool = false, + sendfile: SendfileContext = undefined, request_body: ?*JSC.WebCore.BodyValueRef = null, request_body_buf: std.ArrayListUnmanaged(u8) = .{}, request_body_content_len: usize = 0, - /// Used to avoid looking at the uws.Request struct after it's been freed - is_transfer_encoding: bool = false, - - /// Used to identify if request can be safely deinitialized - is_waiting_body: bool = false, - - /// Used in renderMissing in debug mode to show the user an HTML page - /// Used to avoid looking at the uws.Request struct after it's been freed - is_web_browser_navigation: if (debug_mode) bool else void = if (debug_mode) false else {}, - sink: ?*ResponseStream.JSSink = null, byte_stream: ?*JSC.WebCore.ByteStream = null, /// Used in errors pathname: []const u8 = "", - has_written_status: bool = false, - /// Used either for temporary blob data or fallback /// When the response body is a temporary value response_buf_owned: std.ArrayListUnmanaged(u8) = .{}, - keepalive: bool = true, - // TODO: support builtin compression const can_sendfile = !ssl_enabled; pub fn setAbortHandler(this: *RequestContext) void { - if (this.has_abort_handler) return; + if (this.flags.has_abort_handler) return; if (this.resp) |resp| { - this.has_abort_handler = true; + this.flags.has_abort_handler = true; resp.onAborted(*RequestContext, RequestContext.onAbort, this); } } @@ -1094,7 +1093,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp result.ensureStillAlive(); ctx.pending_promises_for_abort -|= 1; - if (ctx.aborted) { + if (ctx.flags.aborted) { ctx.finalizeForAbort(); return JSValue.jsUndefined(); } @@ -1121,8 +1120,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return; }; ctx.response_jsvalue = value; - std.debug.assert(!ctx.response_protected); - ctx.response_protected = true; + std.debug.assert(!ctx.flags.response_protected); + ctx.flags.response_protected = true; JSC.C.JSValueProtect(ctx.server.globalThis, value.asObjectRef()); ctx.render(response); @@ -1143,7 +1142,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp ctx.pending_promises_for_abort -|= 1; - if (ctx.aborted) { + if (ctx.flags.aborted) { ctx.finalizeForAbort(); return JSValue.jsUndefined(); } @@ -1163,7 +1162,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp value, ); - if (ctx.aborted) { + if (ctx.flags.aborted) { ctx.finalizeForAbort(); return; } @@ -1174,7 +1173,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return; } - if (!resp.hasResponded() and !ctx.has_marked_pending) { + if (!resp.hasResponded() and !ctx.flags.has_marked_pending) { ctx.renderMissing(); return; } @@ -1190,14 +1189,14 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn renderMissingCorked(ctx: *RequestContext) void { if (ctx.resp) |resp| { if (comptime !debug_mode) { - if (!ctx.has_written_status) + if (!ctx.flags.has_written_status) resp.writeStatus("204 No Content"); - ctx.has_written_status = true; + ctx.flags.has_written_status = true; ctx.end("", ctx.shouldCloseConnection()); } else { - if (ctx.is_web_browser_navigation) { + if (ctx.flags.is_web_browser_navigation) { resp.writeStatus("200 OK"); - ctx.has_written_status = true; + ctx.flags.has_written_status = true; resp.writeHeader("content-type", MimeType.html.value); resp.writeHeader("content-encoding", "gzip"); @@ -1206,9 +1205,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return; } - if (!ctx.has_written_status) + if (!ctx.flags.has_written_status) resp.writeStatus("200 OK"); - ctx.has_written_status = true; + ctx.flags.has_written_status = true; ctx.end("Welcome to Bun! To get started, return a Response object.", ctx.shouldCloseConnection()); } } @@ -1222,8 +1221,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp comptime fmt: string, args: anytype, ) void { - if (!this.has_written_status) { - this.has_written_status = true; + if (!this.flags.has_written_status) { + this.flags.has_written_status = true; if (this.resp) |resp| { resp.writeStatus("500 Internal Server Error"); resp.writeHeader("content-type", MimeType.html.value); @@ -1240,7 +1239,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp .reason = .fetch_event_handler, .cwd = VirtualMachine.get().bundler.fs.top_level_dir, .problems = Api.Problems{ - .code = @truncate(u16, @errorToInt(err)), + .code = @truncate(u16, @intFromError(err)), .name = @errorName(err), .exceptions = exceptions, .build = log.toAPI(allocator) catch unreachable, @@ -1265,7 +1264,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return; } - this.has_marked_pending = true; + this.flags.has_marked_pending = true; this.response_buf_owned = std.ArrayListUnmanaged(u8){ .items = bb.items, .capacity = bb.capacity }; if (this.resp) |resp| { @@ -1290,7 +1289,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.response_buf_owned.items.len, this.shouldCloseConnection(), )) { - this.has_marked_pending = true; + this.flags.has_marked_pending = true; resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); this.setAbortHandler(); return; @@ -1314,8 +1313,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn end(this: *RequestContext, data: []const u8, closeConnection: bool) void { if (this.resp) |resp| { - if (this.is_waiting_body) { - this.is_waiting_body = false; + if (this.flags.is_waiting_body) { + this.flags.is_waiting_body = false; resp.clearOnData(); } resp.end(data, closeConnection); @@ -1325,8 +1324,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn endStream(this: *RequestContext, closeConnection: bool) void { if (this.resp) |resp| { - if (this.is_waiting_body) { - this.is_waiting_body = false; + if (this.flags.is_waiting_body) { + this.flags.is_waiting_body = false; resp.clearOnData(); } resp.endStream(closeConnection); @@ -1336,8 +1335,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn endWithoutBody(this: *RequestContext, closeConnection: bool) void { if (this.resp) |resp| { - if (this.is_waiting_body) { - this.is_waiting_body = false; + if (this.flags.is_waiting_body) { + this.flags.is_waiting_body = false; resp.clearOnData(); } resp.endWithoutBody(closeConnection); @@ -1347,7 +1346,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn onWritableResponseBuffer(this: *RequestContext, _: c_ulong, resp: *App.Response) callconv(.C) bool { std.debug.assert(this.resp == resp); - if (this.aborted) { + if (this.flags.aborted) { this.finalizeForAbort(); return false; } @@ -1360,12 +1359,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn onWritableCompleteResponseBufferAndMetadata(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool { std.debug.assert(this.resp == resp); - if (this.aborted) { + if (this.flags.aborted) { this.finalizeForAbort(); return false; } - if (!this.has_written_status) { + if (!this.flags.has_written_status) { this.renderMetadata(); } @@ -1380,7 +1379,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn onWritableCompleteResponseBuffer(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool { std.debug.assert(this.resp == resp); - if (this.aborted) { + if (this.flags.aborted) { this.finalizeForAbort(); return false; } @@ -1417,9 +1416,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn onAbort(this: *RequestContext, resp: *App.Response) void { std.debug.assert(this.resp == resp); - std.debug.assert(!this.aborted); + std.debug.assert(!this.flags.aborted); //mark request as aborted - this.aborted = true; + this.flags.aborted = true; // if signal is not aborted, abort the signal if (this.signal) |signal| { @@ -1456,12 +1455,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // the promise is pending if (body.value.Locked.action != .none or body.value.Locked.promise != null) { this.pending_promises_for_abort += 1; - body.value.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis); } else if (body.value.Locked.readable != null) { body.value.Locked.readable.?.abort(this.server.globalThis); - body.value.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis); body.value.Locked.readable = null; } + body.value.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis); } } @@ -1488,8 +1486,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } pub fn markComplete(this: *RequestContext) void { - if (!this.has_marked_complete) this.server.onRequestComplete(); - this.has_marked_complete = true; + if (!this.flags.has_marked_complete) this.server.onRequestComplete(); + this.flags.has_marked_complete = true; } // This function may be called multiple times @@ -1499,15 +1497,15 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.blob.detach(); if (comptime Environment.allow_assert) { - std.debug.assert(!this.finalized); - this.finalized = true; + std.debug.assert(!this.flags.finalized); + this.flags.finalized = true; } if (!this.response_jsvalue.isEmpty()) { ctxLog("finalizeWithoutDeinit: response_jsvalue != .zero", .{}); - if (this.response_protected) { + if (this.flags.response_protected) { this.response_jsvalue.unprotect(); - this.response_protected = false; + this.flags.response_protected = false; } this.response_jsvalue = JSC.JSValue.zero; } @@ -1515,7 +1513,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // if signal is not aborted, abort the signal if (this.signal) |signal| { this.signal = null; - if (this.aborted and !signal.aborted()) { + if (this.flags.aborted and !signal.aborted()) { const reason = JSC.WebCore.AbortSignal.createAbortError(JSC.ZigString.static("The user aborted a request"), &JSC.ZigString.Empty, this.server.globalThis); reason.ensureStillAlive(); _ = signal.signal(reason); @@ -1558,9 +1556,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // if we are waiting for the body yet and the request was not aborted we can safely clear the onData callback if (this.resp) |resp| { - if (this.is_waiting_body and this.aborted == false) { + if (this.flags.is_waiting_body and this.flags.aborted == false) { resp.clearOnData(); - this.is_waiting_body = false; + this.flags.is_waiting_body = false; } } } @@ -1574,10 +1572,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn deinit(this: *RequestContext) void { ctxLog("deinit<d> ({*})<r>", .{this}); if (comptime Environment.allow_assert) - std.debug.assert(this.finalized); + std.debug.assert(this.flags.finalized); if (comptime Environment.allow_assert) - std.debug.assert(this.has_marked_complete); + std.debug.assert(this.flags.has_marked_complete); var server = this.server; this.request_body_buf.clearAndFree(this.allocator); @@ -1605,8 +1603,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn writeStatus(this: *RequestContext, status: u16) void { var status_text_buf: [48]u8 = undefined; - std.debug.assert(!this.has_written_status); - this.has_written_status = true; + std.debug.assert(!this.flags.has_written_status); + this.flags.has_written_status = true; if (this.resp) |resp| { if (HTTPStatusText.get(status)) |text| { @@ -1635,7 +1633,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp }}; pub fn onSendfile(this: *RequestContext) bool { - if (this.aborted or this.resp == null) { + if (this.flags.aborted or this.resp == null) { this.cleanupAndFinalizeAfterSendfile(); return false; } @@ -1657,7 +1655,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.sendfile.remain -|= @intCast(Blob.SizeType, this.sendfile.offset -| start); - if (errcode != .SUCCESS or this.aborted or this.sendfile.remain == 0 or val == 0) { + if (errcode != .SUCCESS or this.flags.aborted or this.sendfile.remain == 0 or val == 0) { if (errcode != .AGAIN and errcode != .SUCCESS and errcode != .PIPE) { Output.prettyErrorln("Error: {s}", .{@tagName(errcode)}); Output.flush(); @@ -1680,7 +1678,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const wrote = @intCast(Blob.SizeType, sbytes); this.sendfile.offset +|= wrote; this.sendfile.remain -|= wrote; - if (errcode != .AGAIN or this.aborted or this.sendfile.remain == 0 or sbytes == 0) { + if (errcode != .AGAIN or this.flags.aborted or this.sendfile.remain == 0 or sbytes == 0) { if (errcode != .AGAIN and errcode != .SUCCESS and errcode != .PIPE) { Output.prettyErrorln("Error: {s}", .{@tagName(errcode)}); Output.flush(); @@ -1692,7 +1690,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (!this.sendfile.has_set_on_writable) { this.sendfile.has_set_on_writable = true; - this.has_marked_pending = true; + this.flags.has_marked_pending = true; resp.onWritable(*RequestContext, onWritableSendfile, this); } @@ -1704,7 +1702,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn onWritableBytes(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool { std.debug.assert(this.resp == resp); - if (this.aborted) { + if (this.flags.aborted) { this.finalizeForAbort(); return false; } @@ -1725,7 +1723,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.finalize(); return true; } else { - this.has_marked_pending = true; + this.flags.has_marked_pending = true; resp.onWritable(*RequestContext, onWritableBytes, this); return true; } @@ -1739,7 +1737,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.response_buf_owned.items.len = 0; this.finalize(); } else { - this.has_marked_pending = true; + this.flags.has_marked_pending = true; resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); } @@ -1790,11 +1788,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } var err = JSC.Node.Syscall.Error{ - .errno = @intCast(JSC.Node.Syscall.Error.Int, @enumToInt(std.os.E.INVAL)), + .errno = @intCast(JSC.Node.Syscall.Error.Int, @intFromEnum(std.os.E.INVAL)), .syscall = .sendfile, }; var sys = err.withPathLike(file.pathlike).toSystemError(); - sys.message = ZigString.init("MacOS does not support sending non-regular files"); + sys.message = bun.String.static("MacOS does not support sending non-regular files"); this.runErrorHandler(sys.toErrorInstance( this.server.globalThis, )); @@ -1809,11 +1807,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } var err = JSC.Node.Syscall.Error{ - .errno = @intCast(JSC.Node.Syscall.Error.Int, @enumToInt(std.os.E.INVAL)), + .errno = @intCast(JSC.Node.Syscall.Error.Int, @intFromEnum(std.os.E.INVAL)), .syscall = .sendfile, }; var sys = err.withPathLike(file.pathlike).toSystemError(); - sys.message = ZigString.init("File must be regular or FIFO"); + sys.message = bun.String.static("File must be regular or FIFO"); this.runErrorHandler(sys.toErrorInstance( this.server.globalThis, )); @@ -1828,21 +1826,21 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp else @min(original_size, stat_size); - this.needs_content_length = true; + this.flags.needs_content_length = true; this.sendfile = .{ .fd = fd, .remain = this.blob.Blob.offset + original_size, .offset = this.blob.Blob.offset, .auto_close = auto_close, - .socket_fd = if (!this.aborted) resp.getNativeHandle() else -999, + .socket_fd = if (!this.flags.aborted) resp.getNativeHandle() else -999, }; // if we are sending only part of a file, include the content-range header // only include content-range automatically when using a file path instead of an fd // this is to better support manually controlling the behavior if (std.os.S.ISREG(stat.mode) and auto_close) { - this.needs_content_range = (this.sendfile.remain -| this.sendfile.offset) != stat_size; + this.flags.needs_content_range = (this.sendfile.remain -| this.sendfile.offset) != stat_size; } // we know the bounds when we are sending a regular file @@ -1869,14 +1867,14 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } pub fn doSendfile(this: *RequestContext, blob: Blob) void { - if (this.aborted) { + if (this.flags.aborted) { this.finalizeForAbort(); return; } - if (this.has_sendfile_ctx) return; + if (this.flags.has_sendfile_ctx) return; - this.has_sendfile_ctx = true; + this.flags.has_sendfile_ctx = true; if (comptime can_sendfile) { return this.renderSendFile(blob); @@ -1887,7 +1885,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } pub fn onReadFile(this: *RequestContext, result: Blob.Store.ReadFile.ResultType) void { - if (this.aborted or this.resp == null) { + if (this.flags.aborted or this.resp == null) { this.finalizeForAbort(); return; } @@ -1910,8 +1908,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp else @min(original_size, stat_size); - if (!this.has_written_status) - this.needs_content_range = true; + if (!this.flags.has_written_status) + this.flags.needs_content_range = true; // this is used by content-range this.sendfile = .{ @@ -1932,14 +1930,14 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } fn renderWithBlobFromBodyValue(this: *RequestContext) void { - if (this.aborted) { + if (this.flags.aborted) { this.finalizeForAbort(); return; } if (this.blob.needsToReadFile()) { this.req.setYield(false); - if (!this.has_sendfile_ctx) + if (!this.flags.has_sendfile_ctx) this.doSendfile(this.blob.Blob); return; } @@ -1952,7 +1950,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp fn doRenderStream(pair: *StreamPair) void { var this = pair.this; var stream = pair.stream; - if (this.resp == null or this.aborted) { + if (this.resp == null or this.flags.aborted) { stream.value.unprotect(); this.finalizeForAbort(); return; @@ -2003,11 +2001,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } } - this.aborted = this.aborted or response_stream.sink.aborted; + this.flags.aborted = this.flags.aborted or response_stream.sink.aborted; if (assignment_result.toError()) |err_value| { streamLog("returned an error", .{}); - if (!this.aborted) resp.clearAborted(); + if (!this.flags.aborted) resp.clearAborted(); response_stream.detach(); this.sink = null; response_stream.sink.destroy(); @@ -2019,7 +2017,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // TODO: is there a condition where resp could be freed before done? resp.hasResponded()) { - if (!this.aborted) resp.clearAborted(); + if (!this.flags.aborted) resp.clearAborted(); const wrote_anything = response_stream.sink.wrote > 0; streamLog("is done", .{}); const responded = resp.hasResponded(); @@ -2027,10 +2025,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp response_stream.detach(); this.sink = null; response_stream.sink.destroy(); - if (!responded and !wrote_anything and !this.aborted) { + if (!responded and !wrote_anything and !this.flags.aborted) { this.renderMissing(); return; - } else if (wrote_anything and !responded and !this.aborted) { + } else if (wrote_anything and !responded and !this.flags.aborted) { this.endStream(this.shouldCloseConnection()); } @@ -2078,7 +2076,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } } - if (this.aborted) { + if (this.flags.aborted) { response_stream.detach(); stream.cancel(this.server.globalThis); response_stream.sink.done = true; @@ -2113,7 +2111,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const streamLog = Output.scoped(.ReadableStream, false); pub fn didUpgradeWebSocket(this: *RequestContext) bool { - return @ptrToInt(this.upgrade_context) == std.math.maxInt(usize); + return @intFromPtr(this.upgrade_context) == std.math.maxInt(usize); } pub fn onResponse( @@ -2127,7 +2125,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp request_value.ensureStillAlive(); response_value.ensureStillAlive(); - if (ctx.aborted) { + if (ctx.flags.aborted) { ctx.finalizeForAbort(); return; } @@ -2154,19 +2152,19 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (response_value.as(JSC.WebCore.Response)) |response| { ctx.response_jsvalue = response_value; ctx.response_jsvalue.ensureStillAlive(); - ctx.response_protected = false; + ctx.flags.response_protected = false; response.body.value.toBlobIfPossible(); switch (response.body.value) { .Blob => |*blob| { if (blob.needsToReadFile()) { response_value.protect(); - ctx.response_protected = true; + ctx.flags.response_protected = true; } }, .Locked => { response_value.protect(); - ctx.response_protected = true; + ctx.flags.response_protected = true; }, else => {}, } @@ -2204,19 +2202,19 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp ctx.response_jsvalue = fulfilled_value; ctx.response_jsvalue.ensureStillAlive(); - ctx.response_protected = false; + ctx.flags.response_protected = false; ctx.response_ptr = response; response.body.value.toBlobIfPossible(); switch (response.body.value) { .Blob => |*blob| { if (blob.needsToReadFile()) { fulfilled_value.protect(); - ctx.response_protected = true; + ctx.flags.response_protected = true; } }, .Locked => { fulfilled_value.protect(); - ctx.response_protected = true; + ctx.flags.response_protected = true; }, else => {}, } @@ -2259,7 +2257,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } if (ctx.resp) |resp| { // The user returned something that wasn't a promise or a promise with a response - if (!resp.hasResponded() and !ctx.has_marked_pending) ctx.renderMissing(); + if (!resp.hasResponded() and !ctx.flags.has_marked_pending) ctx.renderMissing(); } } @@ -2270,7 +2268,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (req.sink) |wrapper| { wrapper.sink.pending_flush = null; wrapper.sink.done = true; - req.aborted = req.aborted or wrapper.sink.aborted; + req.flags.aborted = req.flags.aborted or wrapper.sink.aborted; wrote_anything = wrapper.sink.wrote > 0; wrapper.sink.finalize(); wrapper.detach(); @@ -2288,7 +2286,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp streamLog("onResolve({any})", .{wrote_anything}); //aborted so call finalizeForAbort - if (req.aborted or req.resp == null) { + if (req.flags.aborted or req.resp == null) { req.finalizeForAbort(); return; } @@ -2326,13 +2324,13 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn handleRejectStream(req: *@This(), globalThis: *JSC.JSGlobalObject, err: JSValue) void { streamLog("handleRejectStream", .{}); - var wrote_anything = req.has_written_status; + var wrote_anything = req.flags.has_written_status; if (req.sink) |wrapper| { wrapper.sink.pending_flush = null; wrapper.sink.done = true; wrote_anything = wrote_anything or wrapper.sink.wrote > 0; - req.aborted = req.aborted or wrapper.sink.aborted; + req.flags.aborted = req.flags.aborted or wrapper.sink.aborted; wrapper.sink.finalize(); wrapper.detach(); req.sink = null; @@ -2349,7 +2347,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp streamLog("onReject({any})", .{wrote_anything}); //aborted so call finalizeForAbort - if (req.aborted) { + if (req.flags.aborted) { req.finalizeForAbort(); return; } @@ -2373,8 +2371,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } const fallback = JSC.SystemError{ - .code = ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_UNHANDLED_ERROR))), - .message = ZigString.init("Unhandled error in ReadableStream"), + .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_UNHANDLED_ERROR))), + .message = bun.String.static("Unhandled error in ReadableStream"), }; req.handleReject(fallback.toErrorInstance(globalThis)); } @@ -2388,7 +2386,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp .Error => { const err = value.Error; _ = value.use(); - if (this.aborted) { + if (this.flags.aborted) { this.finalizeForAbort(); return; } @@ -2406,7 +2404,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return; }, .Locked => |*lock| { - if (this.aborted) { + if (this.flags.aborted) { this.finalizeForAbort(); return; } @@ -2420,8 +2418,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (stream.isLocked(this.server.globalThis)) { streamLog("was locked but it shouldn't be", .{}); var err = JSC.SystemError{ - .code = ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_STREAM_CANNOT_PIPE))), - .message = ZigString.init("Stream already used, please create a new one"), + .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_STREAM_CANNOT_PIPE))), + .message = bun.String.static("Stream already used, please create a new one"), }; stream.value.unprotect(); this.runErrorHandler(err.toErrorInstance(this.server.globalThis)); @@ -2509,7 +2507,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } } - if (this.aborted or this.resp == null) { + if (this.flags.aborted or this.resp == null) { this.finalizeForAbort(); return; } @@ -2528,7 +2526,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } else { // when it's the last one, we just want to know if it's done if (stream.isDone()) { - this.has_marked_pending = true; + this.flags.has_marked_pending = true; resp.onWritable(*RequestContext, onWritableResponseBuffer, this); } } @@ -2540,7 +2538,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // Faster to do the memcpy than to do the two network calls // We are not streaming // This is an important performance optimization - if (this.has_abort_handler and this.blob.fastSize() < 16384 - 1024) { + if (this.flags.has_abort_handler and this.blob.fastSize() < 16384 - 1024) { if (this.resp) |resp| { resp.runCorkedWithType(*RequestContext, doRenderBlobCorked, this); } @@ -2557,7 +2555,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn doRender(this: *RequestContext) void { ctxLog("render", .{}); - if (this.aborted) { + if (this.flags.aborted) { this.finalizeForAbort(); return; } @@ -2569,17 +2567,17 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (this.resp) |resp| { switch (status) { 404 => { - if (!this.has_written_status) { + if (!this.flags.has_written_status) { resp.writeStatus("404 Not Found"); - this.has_written_status = true; + this.flags.has_written_status = true; } this.endWithoutBody(this.shouldCloseConnection()); }, else => { - if (!this.has_written_status) { + if (!this.flags.has_written_status) { resp.writeStatus("500 Internal Server Error"); resp.writeHeader("content-type", "text/plain"); - this.has_written_status = true; + this.flags.has_written_status = true; } this.end("Something went wrong!", this.shouldCloseConnection()); @@ -2600,7 +2598,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (this.pathname.len > 0) return this.pathname; - if (!this.has_abort_handler) { + if (!this.flags.has_abort_handler) { return this.req.url(); } @@ -2643,8 +2641,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp status: u16, ) void { JSC.markBinding(@src()); - if (!this.server.config.onError.isEmpty() and !this.has_called_error_handler) { - this.has_called_error_handler = true; + if (!this.server.config.onError.isEmpty() and !this.flags.has_called_error_handler) { + this.flags.has_called_error_handler = true; var args = [_]JSC.C.JSValueRef{value.asObjectRef()}; const result = JSC.C.JSObjectCallAsFunctionReturnValue(this.server.globalThis, this.server.config.onError.asObjectRef(), this.server.thisObject.asObjectRef(), 1, &args); defer result.ensureStillAlive(); @@ -2679,7 +2677,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp var response: *JSC.WebCore.Response = this.response_ptr.?; var status = response.statusCode(); - var needs_content_range = this.needs_content_range and this.sendfile.remain < this.blob.size(); + var needs_content_range = this.flags.needs_content_range and this.sendfile.remain < this.blob.size(); const size = if (needs_content_range) this.sendfile.remain @@ -2744,26 +2742,22 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // 1. Bun.file("foo") // 2. The content-disposition header is not present if (!has_content_disposition and content_type.category.autosetFilename()) { - if (this.blob.store()) |store| { - if (store.data == .file) { - if (store.data.file.pathlike == .path) { - const basename = std.fs.path.basename(store.data.file.pathlike.path.slice()); - if (basename.len > 0) { - var filename_buf: [1024]u8 = undefined; - - resp.writeHeader( - "content-disposition", - std.fmt.bufPrint(&filename_buf, "filename=\"{s}\"", .{basename[0..@min(basename.len, 1024 - 32)]}) catch "", - ); - } - } + if (this.blob.getFileName()) |filename| { + const basename = std.fs.path.basename(filename); + if (basename.len > 0) { + var filename_buf: [1024]u8 = undefined; + + resp.writeHeader( + "content-disposition", + std.fmt.bufPrint(&filename_buf, "filename=\"{s}\"", .{basename[0..@min(basename.len, 1024 - 32)]}) catch "", + ); } } } - if (this.needs_content_length) { + if (this.flags.needs_content_length) { resp.writeHeaderInt("content-length", size); - this.needs_content_length = false; + this.flags.needs_content_length = false; } if (needs_content_range) { @@ -2780,7 +2774,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp .{ this.sendfile.offset, this.sendfile.offset + (this.sendfile.remain -| 1) }, ) catch "bytes */*", ); - this.needs_content_range = false; + this.flags.needs_content_range = false; } } @@ -2794,7 +2788,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp bytes.len, this.shouldCloseConnection(), )) { - this.has_marked_pending = true; + this.flags.has_marked_pending = true; resp.onWritable(*RequestContext, onWritableBytes, this); // given a blob, we might not have set an abort handler yet this.setAbortHandler(); @@ -2817,8 +2811,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp std.debug.assert(this.resp == resp); - this.is_waiting_body = last == false; - if (this.aborted or this.has_marked_complete) return; + this.flags.is_waiting_body = last == false; + if (this.flags.aborted or this.flags.has_marked_complete) return; if (this.request_body != null) { var body = this.request_body.?; @@ -2874,7 +2868,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const prev_len = bytes.items.len; bytes.items.len = total; var slice = bytes.items[prev_len..]; - @memcpy(slice.ptr, chunk.ptr, chunk.len); + @memcpy(slice[0..chunk.len], chunk); body.value = .{ .InternalBlob = .{ .bytes = bytes.toManaged(this.allocator), @@ -2898,7 +2892,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn onStartStreamingRequestBody(this: *RequestContext) JSC.WebCore.DrainResult { ctxLog("onStartStreamingRequestBody", .{}); - if (this.aborted) { + if (this.flags.aborted) { return JSC.WebCore.DrainResult{ .aborted = {}, }; @@ -2928,7 +2922,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp ctxLog("onStartBuffering", .{}); // TODO: check if is someone calling onStartBuffering other than onStartBufferingCallback // if is not, this should be removed and only keep protect + setAbortHandler - if (this.is_transfer_encoding == false and this.request_body_content_len == 0) { + if (this.flags.is_transfer_encoding == false and this.request_body_content_len == 0) { // no content-length or 0 content-length // no transfer-encoding if (this.request_body != null) { @@ -3200,7 +3194,7 @@ pub const WebSocketServer = struct { globalObject.throwInvalidArguments("websocket expects maxPayloadLength to be an integer", .{}); return null; } - server.maxPayloadLength = @intCast(u32, @truncate(i33, @max(value.toInt64(), 0))); + server.maxPayloadLength = @intCast(u32, @max(value.toInt64(), 0)); } } if (object.get(globalObject, "idleTimeout")) |value| { @@ -3220,7 +3214,7 @@ pub const WebSocketServer = struct { return null; } - server.backpressureLimit = @intCast(u32, @truncate(i33, @max(value.toInt64(), 0))); + server.backpressureLimit = @intCast(u32, @max(value.toInt64(), 0)); } } // if (object.get(globalObject, "sendPings")) |value| { @@ -3366,7 +3360,7 @@ pub const ServerWebSocket = struct { opcode: uws.Opcode, ) void { log("onMessage({d}): {s}", .{ - @enumToInt(opcode), + @intFromEnum(opcode), message, }); const onMessageHandler = this.handler.onMessage; @@ -3566,11 +3560,6 @@ pub const ServerWebSocket = struct { if (message_value.asArrayBuffer(globalThis)) |array_buffer| { const buffer = array_buffer.slice(); - if (buffer.len == 0) { - globalThis.throw("publish requires a non-empty message", .{}); - return .zero; - } - const result = if (!publish_to_self) this.websocket.publish(topic_slice.slice(), buffer, .binary, compress) else @@ -3586,9 +3575,6 @@ pub const ServerWebSocket = struct { { var string_slice = message_value.toSlice(globalThis, bun.default_allocator); defer string_slice.deinit(); - if (string_slice.len == 0) { - return JSValue.jsNumber(0); - } const buffer = string_slice.slice(); @@ -3640,10 +3626,6 @@ pub const ServerWebSocket = struct { var topic_slice = topic_value.toSlice(globalThis, bun.default_allocator); defer topic_slice.deinit(); - if (topic_slice.len == 0) { - globalThis.throw("publishText requires a non-empty topic", .{}); - return .zero; - } const compress = args.len > 1 and compress_value.toBoolean(); @@ -3654,9 +3636,6 @@ pub const ServerWebSocket = struct { var string_slice = message_value.toSlice(globalThis, bun.default_allocator); defer string_slice.deinit(); - if (string_slice.len == 0) { - return JSValue.jsNumber(0); - } const buffer = string_slice.slice(); @@ -3721,10 +3700,6 @@ pub const ServerWebSocket = struct { }; const buffer = array_buffer.slice(); - if (buffer.len == 0) { - return JSC.JSValue.jsNumber(0); - } - const result = if (!publish_to_self) this.websocket.publish(topic_slice.slice(), buffer, .binary, compress) else @@ -3889,10 +3864,6 @@ pub const ServerWebSocket = struct { } if (message_value.asArrayBuffer(globalThis)) |buffer| { - if (buffer.len == 0) { - return JSValue.jsNumber(0); - } - switch (this.websocket.send(buffer.slice(), .binary, compress, true)) { .backpressure => { log("send() backpressure ({d} bytes)", .{buffer.len}); @@ -3912,9 +3883,6 @@ pub const ServerWebSocket = struct { { var string_slice = message_value.toSlice(globalThis, bun.default_allocator); defer string_slice.deinit(); - if (string_slice.len == 0) { - return JSValue.jsNumber(0); - } const buffer = string_slice.slice(); switch (this.websocket.send(buffer, .text, compress, true)) { @@ -3966,9 +3934,6 @@ pub const ServerWebSocket = struct { var string_slice = message_value.toSlice(globalThis, bun.default_allocator); defer string_slice.deinit(); - if (string_slice.len == 0) { - return JSValue.jsNumber(0); - } const buffer = string_slice.slice(); switch (this.websocket.send(buffer, .text, compress, true)) { @@ -4000,9 +3965,6 @@ pub const ServerWebSocket = struct { var string_slice = message_str.toSlice(globalThis, bun.default_allocator); defer string_slice.deinit(); - if (string_slice.len == 0) { - return JSValue.jsNumber(0); - } const buffer = string_slice.slice(); switch (this.websocket.send(buffer, .text, compress, true)) { @@ -4049,10 +4011,6 @@ pub const ServerWebSocket = struct { return .zero; }; - if (buffer.len == 0) { - return JSValue.jsNumber(0); - } - switch (this.websocket.send(buffer.slice(), .binary, compress, true)) { .backpressure => { log("sendBinary() backpressure ({d} bytes)", .{buffer.len}); @@ -4082,10 +4040,6 @@ pub const ServerWebSocket = struct { const buffer = array_buffer.slice(); - if (buffer.len == 0) { - return JSValue.jsNumber(0); - } - switch (this.websocket.send(buffer, .binary, compress, true)) { .backpressure => { log("sendBinary() backpressure ({d} bytes)", .{buffer.len}); @@ -4422,36 +4376,23 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { const compress = (compress_value orelse JSValue.jsBoolean(true)).toBoolean(); - if (message_value.isEmptyOrUndefinedOrNull()) { - JSC.JSError(this.vm.allocator, "publish requires a non-empty message", .{}, globalThis, exception); - return .zero; - } - if (message_value.asArrayBuffer(globalThis)) |buffer| { - if (buffer.len == 0) { - JSC.JSError(this.vm.allocator, "publish requires a non-empty message", .{}, globalThis, exception); - return .zero; - } - return JSValue.jsNumber( // if 0, return 0 // else return number of bytes sent - @as(i32, @boolToInt(uws.AnyWebSocket.publishWithOptions(ssl_enabled, app, topic_slice.slice(), buffer.slice(), .binary, compress))) * @intCast(i32, @truncate(u31, buffer.len)), + @as(i32, @intFromBool(uws.AnyWebSocket.publishWithOptions(ssl_enabled, app, topic_slice.slice(), buffer.slice(), .binary, compress))) * @intCast(i32, @truncate(u31, buffer.len)), ); } { var string_slice = message_value.toSlice(globalThis, bun.default_allocator); defer string_slice.deinit(); - if (string_slice.len == 0) { - return JSValue.jsNumber(0); - } const buffer = string_slice.slice(); return JSValue.jsNumber( // if 0, return 0 // else return number of bytes sent - @as(i32, @boolToInt(uws.AnyWebSocket.publishWithOptions(ssl_enabled, app, topic_slice.slice(), buffer, .text, compress))) * @intCast(i32, @truncate(u31, buffer.len)), + @as(i32, @intFromBool(uws.AnyWebSocket.publishWithOptions(ssl_enabled, app, topic_slice.slice(), buffer, .text, compress))) * @intCast(i32, @truncate(u31, buffer.len)), ); } @@ -4484,11 +4425,11 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { } var upgrader = bun.cast(*RequestContext, request.upgrader.?); - if (upgrader.aborted or upgrader.resp == null) { + if (upgrader.flags.aborted or upgrader.resp == null) { return JSC.jsBoolean(false); } - if (upgrader.upgrade_context == null or @ptrToInt(upgrader.upgrade_context) == std.math.maxInt(usize)) { + if (upgrader.upgrade_context == null or @intFromPtr(upgrader.upgrade_context) == std.math.maxInt(usize)) { return JSC.jsBoolean(false); } const resp = upgrader.resp.?; @@ -4582,7 +4523,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { // See https://github.com/oven-sh/bun/issues/1339 // obviously invalid pointer marks it as used - upgrader.upgrade_context = @intToPtr(*uws.uws_socket_context_s, std.math.maxInt(usize)); + upgrader.upgrade_context = @ptrFromInt(*uws.uws_socket_context_s, std.math.maxInt(usize)); request.upgrader = null; resp.clearAborted(); @@ -4947,7 +4888,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { if (reason.len == 0) { break; } - @memcpy(output_buf[written..].ptr, reason.ptr, reason.len); + @memcpy(output_buf[written..][0..reason.len], reason); written += reason.len; } @@ -4958,7 +4899,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { if (reason.len > 0) { output_buf[written..][0.." via ".len].* = " via ".*; written += " via ".len; - @memcpy(output_buf[written..].ptr, reason.ptr, reason.len); + @memcpy(output_buf[written..][0..reason.len], reason); written += reason.len; } } @@ -4970,7 +4911,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { if (reason.len > 0) { output_buf[written..][0] = ' '; written += 1; - @memcpy(output_buf[written..].ptr, reason.ptr, reason.len); + @memcpy(output_buf[written..][0..reason.len], reason); written += reason.len; } } @@ -5110,7 +5051,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { }; if (comptime debug_mode) { - ctx.is_web_browser_navigation = brk: { + ctx.flags.is_web_browser_navigation = brk: { if (ctx.req.header("sec-fetch-dest")) |fetch_dest| { if (strings.eqlComptime(fetch_dest, "document")) { break :brk true; @@ -5141,8 +5082,8 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { } ctx.request_body_content_len = req_len; - ctx.is_transfer_encoding = req.header("transfer-encoding") != null; - if (req_len > 0 or ctx.is_transfer_encoding) { + ctx.flags.is_transfer_encoding = req.header("transfer-encoding") != null; + if (req_len > 0 or ctx.flags.is_transfer_encoding) { // we defer pre-allocating the body until we receive the first chunk // that way if the client is lying about how big the body is or the client aborts // we don't waste memory @@ -5154,7 +5095,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { .onStartStreaming = RequestContext.onStartStreamingRequestBodyCallback, }, }; - ctx.is_waiting_body = true; + ctx.flags.is_waiting_body = true; resp.onData(*RequestContext, RequestContext.onBufferedBodyChunk, ctx); } } diff --git a/src/bun.js/api/sockets.classes.ts b/src/bun.js/api/sockets.classes.ts index da07741a3..5bd073b9f 100644 --- a/src/bun.js/api/sockets.classes.ts +++ b/src/bun.js/api/sockets.classes.ts @@ -15,10 +15,17 @@ function generate(ssl) { authorized: { getter: "getAuthorized", }, + alpnProtocol: { + getter: "getALPNProtocol", + }, write: { fn: "write", length: 3, }, + upgradeTLS: { + fn: "upgradeTLS", + length: 1, + }, end: { fn: "end", length: 3, @@ -82,6 +89,11 @@ function generate(ssl) { fn: "reload", length: 1, }, + + setServername: { + fn: "setServername", + length: 1, + }, }, finalize: true, construct: true, |