node:net: implement BlockList (#19277)

This commit is contained in:
Meghan Denny
2025-05-01 15:09:44 -08:00
committed by GitHub
parent 5874cc44d3
commit 9e201eff9e
24 changed files with 840 additions and 116 deletions

View File

@@ -42,6 +42,7 @@ pub const TCPSocket = @import("api/bun/socket.zig").TCPSocket;
pub const TLSSocket = @import("api/bun/socket.zig").TLSSocket;
pub const UDPSocket = @import("api/bun/udp_socket.zig").UDPSocket;
pub const Valkey = @import("../valkey/js_valkey.zig").JSValkeyClient;
pub const BlockList = @import("./node/net/BlockList.zig");
pub const napi = @import("../napi/napi.zig");

View File

@@ -5,6 +5,7 @@
//! TODO: add a inspect method (under `Symbol.for("nodejs.util.inspect.custom")`).
//! Requires updating bindgen.
const SocketAddress = @This();
const validators = @import("./../../../node/util/validators.zig");
pub const js = JSC.Codegen.JSSocketAddress;
pub const toJS = js.toJS;
pub const fromJS = js.fromJS;
@@ -47,39 +48,7 @@ pub const Options = struct {
const _family: AF = if (try obj.get(global, "family")) |fam| blk: {
// "ipv4" or "ipv6", ignoring case
if (fam.isString()) {
const fam_str = try bun.String.fromJS(fam, global);
defer fam_str.deref();
if (fam_str.length() != 4)
return throwBadFamilyIP(global, fam);
if (fam_str.is8Bit()) {
const slice = fam_str.latin1();
if (std.ascii.eqlIgnoreCase(slice[0..4], "ipv4")) {
break :blk AF.INET;
} else if (std.ascii.eqlIgnoreCase(slice[0..4], "ipv6")) {
break :blk AF.INET6;
} else return throwBadFamilyIP(global, fam);
} else {
// not full ignore-case since that would require converting
// utf16 -> latin1 and the allocation isn't worth it.
if (fam_str.eqlComptime("ipv4") or fam_str.eqlComptime("IPv4")) {
break :blk AF.INET;
} else if (fam_str.eqlComptime("ipv6") or fam_str.eqlComptime("IPv6")) {
break :blk AF.INET6;
} else {
return throwBadFamilyIP(global, fam);
}
}
} else if (fam.isUInt32AsAnyInt()) {
break :blk switch (fam.toU32()) {
AF.INET.int() => AF.INET,
AF.INET6.int() => AF.INET6,
else => return global.throwInvalidArgumentPropertyValue("options.family", "AF_INET or AF_INET6", fam),
};
} else {
return global.throwInvalidArgumentPropertyValue("options.family", "a string or number", fam);
}
break :blk try .fromJS(global, fam);
} else AF.INET;
// required. Validated by `validatePort`.
@@ -110,9 +79,6 @@ pub const Options = struct {
};
}
inline fn throwBadFamilyIP(global: *JSC.JSGlobalObject, family_: JSC.JSValue) bun.JSError {
return global.throwInvalidArgumentPropertyValue("options.family", "'ipv4' or 'ipv6'", family_);
}
inline fn throwBadPort(global: *JSC.JSGlobalObject, port_: JSC.JSValue) bun.JSError {
const ty = global.determineSpecificType(port_) catch {
return global.ERR(.SOCKET_BAD_PORT, "The \"options.port\" argument must be a valid IP port number.", .{}).throw();
@@ -218,6 +184,16 @@ pub fn constructor(global: *JSC.JSGlobalObject, frame: *JSC.CallFrame) bun.JSErr
return SocketAddress.create(global, options);
}
pub fn initFromAddrFamily(global: *JSC.JSGlobalObject, address_js: JSValue, family_js: JSValue) bun.JSError!SocketAddress {
if (!address_js.isString()) return global.throwInvalidArgumentTypeValue("options.address", "string", address_js);
const address_: bun.String = try .fromJS(address_js, global);
const family_: AF = try .fromJS(global, family_js);
return .initJS(global, .{
.address = address_,
.family = family_,
});
}
/// Semi-structured JS api for creating a `SocketAddress`. If you have raw
/// socket address data, prefer `SocketAddress.new`.
///
@@ -225,6 +201,10 @@ pub fn constructor(global: *JSC.JSGlobalObject, frame: *JSC.CallFrame) bun.JSErr
/// - `options.address` gets moved, much like `adoptRef`. Do not `deref` it
/// after passing it in.
pub fn create(global: *JSC.JSGlobalObject, options: Options) bun.JSError!*SocketAddress {
return .new(try .initJS(global, options));
}
pub fn initJS(global: *JSC.JSGlobalObject, options: Options) bun.JSError!SocketAddress {
var presentation: bun.String = .empty;
// We need a zero-terminated cstring for `ares_inet_pton`, which forces us to
@@ -272,10 +252,10 @@ pub fn create(global: *JSC.JSGlobalObject, options: Options) bun.JSError!*Socket
},
};
return SocketAddress.new(.{
return .{
._addr = addr,
._presentation = presentation,
});
};
}
pub const AddressError = error{
@@ -399,20 +379,9 @@ pub fn getAddress(this: *SocketAddress, global: *JSC.JSGlobalObject) JSC.JSValue
/// - use this impl in server.zig
pub fn address(this: *SocketAddress) bun.String {
if (this._presentation.tag != .Dead) return this._presentation;
var buf: [inet.INET6_ADDRSTRLEN]u8 = undefined;
const addr_src: *const anyopaque = if (this.family() == AF.INET)
@ptrCast(&this.asV4().addr)
else
@ptrCast(&this.asV6().addr);
const formatted = std.mem.span(ares.ares_inet_ntop(this.family().int(), addr_src, &buf, buf.len) orelse {
std.debug.panic("Invariant violation: SocketAddress created with invalid IPv6 address ({any})", .{this._addr});
});
if (comptime bun.Environment.isDebug) {
bun.assertWithLocation(bun.strings.isAllASCII(formatted), @src());
}
const presentation = bun.webcore.encoding.toBunStringComptime(formatted, .latin1);
const formatted = this._addr.fmt(&buf);
const presentation = JSC.WebCore.encoding.toBunStringComptime(formatted, .latin1);
bun.debugAssert(presentation.tag != .Dead);
this._presentation = presentation;
return presentation;
@@ -535,9 +504,46 @@ const ipv6: bun.String = .{ .tag = .WTFStringImpl, .value = .{ .WTFStringImpl =
pub const AF = enum(inet.sa_family_t) {
INET = @intCast(inet.AF_INET),
INET6 = @intCast(inet.AF_INET6),
pub inline fn int(this: AF) inet.sa_family_t {
return @intFromEnum(this);
}
pub fn fromJS(global: *JSC.JSGlobalObject, value: JSValue) !AF {
if (value.isString()) {
const fam_str = try bun.String.fromJS(value, global);
defer fam_str.deref();
if (fam_str.length() != 4) return global.throwInvalidArgumentPropertyValue("options.family", "'ipv4' or 'ipv6'", value);
if (fam_str.is8Bit()) {
const slice = fam_str.latin1();
if (std.ascii.eqlIgnoreCase(slice[0..4], "ipv4")) return AF.INET;
if (std.ascii.eqlIgnoreCase(slice[0..4], "ipv6")) return AF.INET6;
return global.throwInvalidArgumentPropertyValue("options.family", "'ipv4' or 'ipv6'", value);
} else {
// not full ignore-case since that would require converting
// utf16 -> latin1 and the allocation isn't worth it.
if (fam_str.eqlComptime("ipv4") or fam_str.eqlComptime("IPv4")) return AF.INET;
if (fam_str.eqlComptime("ipv6") or fam_str.eqlComptime("IPv6")) return AF.INET6;
return global.throwInvalidArgumentPropertyValue("options.family", "'ipv4' or 'ipv6'", value);
}
} else if (value.isUInt32AsAnyInt()) {
return switch (value.toU32()) {
AF.INET.int() => AF.INET,
AF.INET6.int() => AF.INET6,
else => return global.throwInvalidArgumentPropertyValue("options.family", "AF_INET or AF_INET6", value),
};
} else {
return global.throwInvalidArgumentPropertyValue("options.family", "a string or number", value);
}
}
pub fn upper(this: AF) [:0]const u8 {
return switch (this) {
.INET => "IPv4",
.INET6 => "IPv6",
};
}
};
/// ## Notes
@@ -545,7 +551,7 @@ pub const AF = enum(inet.sa_family_t) {
/// They're no longer the same size.
/// - This replaces `sockaddr_storage` because it's huge. This is 28 bytes,
/// while `sockaddr_storage` is 128 bytes.
const sockaddr = extern union {
pub const sockaddr = extern union {
sin: inet.sockaddr_in,
sin6: inet.sockaddr_in6,
@@ -574,11 +580,42 @@ const sockaddr = extern union {
} };
}
pub fn as_v4(self: *const sockaddr) ?u32 {
if (self.sin.family == std.posix.AF.INET) return self.sin.addr;
if (self.sin.family == std.posix.AF.INET6) {
if (!std.mem.allEqual(u8, self.sin6.addr[0..10], 0)) return null;
if (self.sin6.addr[10] != 255) return null;
if (self.sin6.addr[11] != 255) return null;
return @bitCast(self.sin6.addr[12..16].*);
}
return null;
}
pub fn family(self: *const sockaddr) AF {
return switch (self.sin.family) {
std.posix.AF.INET => .INET,
std.posix.AF.INET6 => .INET6,
else => unreachable,
};
}
pub fn fmt(self: *const sockaddr, buf: *[inet.INET6_ADDRSTRLEN]u8) [:0]const u8 {
const addr_src: *const anyopaque = if (self.family() == AF.INET) @ptrCast(&self.sin.addr) else @ptrCast(&self.sin6.addr);
const formatted = std.mem.sliceTo(ares.ares_inet_ntop(self.family().int(), addr_src, buf, buf.len) orelse {
std.debug.panic("Invariant violation: SocketAddress created with invalid IPv6 address ({any})", .{self});
}, 0);
if (comptime bun.Environment.isDebug) bun.assertWithLocation(bun.strings.isAllASCII(formatted), @src());
return formatted;
}
// I'd be money endianess is going to screw us here.
pub const @"127.0.0.1": sockaddr = sockaddr.v4(0, @bitCast([_]u8{ 127, 0, 0, 1 }));
// TODO: check that `::` is all zeroes on all platforms. Should correspond
// to `IN6ADDR_ANY_INIT`.
pub const @"::": sockaddr = sockaddr.v6(0, inet.IN6ADDR_ANY_INIT, 0, 0);
pub const in = inet.sockaddr_in;
pub const in6 = inet.sockaddr_in6;
};
const WellKnownAddress = struct {
@@ -631,7 +668,7 @@ const JSValue = JSC.JSValue;
const isDebug = bun.Environment.isDebug;
const allow_assert = bun.Environment.allow_assert;
const inet = if (bun.Environment.isWindows)
pub const inet = if (bun.Environment.isWindows)
win: {
const ws2 = std.os.windows.ws2_32;
break :win struct {

View File

@@ -448,4 +448,41 @@ export default [
},
},
}),
define({
name: "BlockList",
construct: true,
call: false,
finalize: true,
estimatedSize: true,
// customInspect: true,
structuredClone: { transferable: false, tag: 251 },
JSType: "0b11101110",
klass: {
isBlockList: {
fn: "isBlockList",
length: 1,
},
},
proto: {
addAddress: {
fn: "addAddress",
length: 1,
},
addRange: {
fn: "addRange",
length: 2,
},
addSubnet: {
fn: "addSubnet",
length: 2,
},
check: {
fn: "check",
length: 1,
},
rules: {
getter: "rules",
},
},
}),
];

View File

@@ -89,6 +89,17 @@ pub const JSGlobalObject = opaque {
return this.ERR(.INVALID_ARG_VALUE, "The \"{s}\" argument is invalid. Received {}", .{ argname, actual_string_value }).throw();
}
pub fn throwInvalidArgumentValueCustom(
this: *JSGlobalObject,
argname: []const u8,
value: JSValue,
message: []const u8,
) bun.JSError {
const actual_string_value = try determineSpecificType(this, value);
defer actual_string_value.deref();
return this.ERR(.INVALID_ARG_VALUE, "The \"{s}\" argument {s}. Received {}", .{ argname, message, actual_string_value }).throw();
}
/// Throw an `ERR_INVALID_ARG_VALUE` when the invalid value is a property of an object.
/// Message depends on whether `expected` is present.
/// - "The property "{argname}" is invalid. Received {value}"

View File

@@ -81,9 +81,9 @@ pub const Classes = struct {
pub const NodeHTTPResponse = api.NodeHTTPResponse;
pub const FrameworkFileSystemRouter = bun.bake.FrameworkRouter.JSFrameworkRouter;
pub const DNSResolver = api.DNS.DNSResolver;
pub const S3Client = webcore.S3Client;
pub const S3Stat = webcore.S3Stat;
pub const HTMLBundle = api.HTMLBundle;
pub const RedisClient = api.Valkey;
pub const BlockList = api.BlockList;
};

View File

@@ -243,6 +243,7 @@ enum SerializationTag {
// bun types start at 254 and decrease with each addition
Bun__X509CertificateTag = 253,
Bun__KeyObjectTag = 252,
Bun__nodenet_BlockList = 251,
ErrorTag = 255
};

View File

@@ -0,0 +1,240 @@
const std = @import("std");
const bun = @import("bun");
const C = bun.c;
const Environment = bun.Environment;
const JSC = bun.JSC;
const string = bun.string;
const Output = bun.Output;
const ZigString = JSC.ZigString;
const validators = @import("./../util/validators.zig");
const SocketAddress = bun.JSC.GeneratedClassesList.SocketAddress;
const sockaddr = SocketAddress.sockaddr;
const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{});
pub const new = bun.TrivialNew(@This());
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
const js = JSC.Codegen.JSBlockList;
pub const fromJS = js.fromJS;
pub const toJS = js.toJS;
ref_count: RefCount = .init(),
globalThis: *JSC.JSGlobalObject,
da_rules: std.ArrayList(Rule),
mutex: bun.Mutex = .{},
pub fn constructor(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!*@This() {
_ = callFrame;
const ptr = @This().new(.{
.globalThis = globalThis,
.da_rules = .init(bun.default_allocator),
});
return ptr;
}
pub fn estimatedSize(this: *@This()) usize {
this.mutex.lock();
defer this.mutex.unlock();
return @sizeOf(@This()) + (@sizeOf(Rule) * this.da_rules.items.len);
}
pub fn finalize(this: *@This()) void {
this.deref();
}
pub fn deinit(this: *@This()) void {
this.da_rules.deinit();
bun.destroy(this);
}
pub fn isBlockList(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
_ = globalThis;
const value = callframe.argumentsAsArray(1)[0];
return .jsBoolean(value.as(@This()) != null);
}
pub fn addAddress(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
this.mutex.lock();
defer this.mutex.unlock();
const arguments = callframe.argumentsAsArray(2);
const address_js, var family_js = arguments;
if (family_js.isUndefined()) family_js = bun.String.static("ipv4").toJS(globalThis);
const address = if (address_js.as(SocketAddress)) |sa| sa._addr else blk: {
try validators.validateString(globalThis, address_js, "address", .{});
try validators.validateString(globalThis, family_js, "family", .{});
break :blk (try SocketAddress.initFromAddrFamily(globalThis, address_js, family_js))._addr;
};
try this.da_rules.insert(0, .{ .addr = address });
return .jsUndefined();
}
pub fn addRange(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
this.mutex.lock();
defer this.mutex.unlock();
const arguments = callframe.argumentsAsArray(3);
const start_js, const end_js, var family_js = arguments;
if (family_js.isUndefined()) family_js = bun.String.static("ipv4").toJS(globalThis);
const start = if (start_js.as(SocketAddress)) |sa| sa._addr else blk: {
try validators.validateString(globalThis, start_js, "start", .{});
try validators.validateString(globalThis, family_js, "family", .{});
break :blk (try SocketAddress.initFromAddrFamily(globalThis, start_js, family_js))._addr;
};
const end = if (end_js.as(SocketAddress)) |sa| sa._addr else blk: {
try validators.validateString(globalThis, end_js, "end", .{});
try validators.validateString(globalThis, family_js, "family", .{});
break :blk (try SocketAddress.initFromAddrFamily(globalThis, end_js, family_js))._addr;
};
if (_compare(start, end)) |ord| {
if (ord.compare(.gt)) {
return globalThis.throwInvalidArgumentValueCustom("start", start_js, "must come before end");
}
}
try this.da_rules.insert(0, .{ .range = .{ .start = start, .end = end } });
return .jsUndefined();
}
pub fn addSubnet(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
this.mutex.lock();
defer this.mutex.unlock();
const arguments = callframe.argumentsAsArray(3);
const network_js, const prefix_js, var family_js = arguments;
if (family_js.isUndefined()) family_js = bun.String.static("ipv4").toJS(globalThis);
const network = if (network_js.as(SocketAddress)) |sa| sa._addr else blk: {
try validators.validateString(globalThis, network_js, "network", .{});
try validators.validateString(globalThis, family_js, "family", .{});
break :blk (try SocketAddress.initFromAddrFamily(globalThis, network_js, family_js))._addr;
};
var prefix: u8 = 0;
switch (network.sin.family) {
std.posix.AF.INET => prefix = @intCast(try validators.validateInt32(globalThis, prefix_js, "prefix", .{}, 0, 32)),
std.posix.AF.INET6 => prefix = @intCast(try validators.validateInt32(globalThis, prefix_js, "prefix", .{}, 0, 128)),
else => {},
}
try this.da_rules.insert(0, .{ .subnet = .{ .network = network, .prefix = prefix } });
return .jsUndefined();
}
pub fn check(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
this.mutex.lock();
defer this.mutex.unlock();
const arguments = callframe.argumentsAsArray(2);
const address_js, var family_js = arguments;
if (family_js.isUndefined()) family_js = bun.String.static("ipv4").toJS(globalThis);
const address = if (address_js.as(SocketAddress)) |sa| sa._addr else blk: {
try validators.validateString(globalThis, address_js, "address", .{});
try validators.validateString(globalThis, family_js, "family", .{});
break :blk (SocketAddress.initFromAddrFamily(globalThis, address_js, family_js) catch |err| {
bun.debugAssert(err == error.JSError);
globalThis.clearException();
return .jsBoolean(false);
})._addr;
};
for (this.da_rules.items) |item| {
switch (item) {
.addr => |a| {
const order = _compare(address, a) orelse continue;
if (order.compare(.eq)) return .jsBoolean(true);
},
.range => |r| {
const os = _compare(address, r.start) orelse continue;
const oe = _compare(address, r.end) orelse continue;
if (os.compare(.gte) and oe.compare(.lte)) return .jsBoolean(true);
},
.subnet => |s| {
if (address.as_v4()) |ip_addr| if (s.network.as_v4()) |subnet_addr| {
if (s.prefix == 32) if (ip_addr == subnet_addr) (return .jsBoolean(true)) else continue;
const one: u32 = 1;
const mask_addr = ((one << @intCast(s.prefix)) - 1) << @intCast(32 - s.prefix);
const ip_net: u32 = @byteSwap(ip_addr) & mask_addr;
const subnet_net: u32 = @byteSwap(subnet_addr) & mask_addr;
if (ip_net == subnet_net) return .jsBoolean(true);
};
if (address.sin.family == std.posix.AF.INET6 and s.network.sin.family == std.posix.AF.INET6) {
const ip_addr: u128 = @bitCast(address.sin6.addr);
const subnet_addr: u128 = @bitCast(s.network.sin6.addr);
if (s.prefix == 128) if (ip_addr == subnet_addr) (return .jsBoolean(true)) else continue;
const one: u128 = 1;
const mask_addr = ((one << @intCast(s.prefix)) - 1) << @intCast(128 - s.prefix);
const ip_net: u128 = @byteSwap(ip_addr) & mask_addr;
const subnet_net: u128 = @byteSwap(subnet_addr) & mask_addr;
if (ip_net == subnet_net) return .jsBoolean(true);
}
},
}
}
return .jsBoolean(false);
}
pub fn rules(this: *@This(), globalThis: *JSC.JSGlobalObject) JSC.JSValue {
this.mutex.lock();
defer this.mutex.unlock();
var list = std.ArrayList(JSC.JSValue).initCapacity(bun.default_allocator, this.da_rules.items.len) catch bun.outOfMemory();
defer list.deinit();
for (this.da_rules.items) |rule| {
switch (rule) {
.addr => |a| {
var buf: [SocketAddress.inet.INET6_ADDRSTRLEN]u8 = @splat(0);
list.appendAssumeCapacity(bun.String.createFormatForJS(globalThis, "Address: {s} {s}", .{ a.family().upper(), a.fmt(&buf) }));
},
.range => |r| {
var buf_s: [SocketAddress.inet.INET6_ADDRSTRLEN]u8 = @splat(0);
var buf_e: [SocketAddress.inet.INET6_ADDRSTRLEN]u8 = @splat(0);
list.appendAssumeCapacity(bun.String.createFormatForJS(globalThis, "Range: {s} {s}-{s}", .{ r.start.family().upper(), r.start.fmt(&buf_s), r.end.fmt(&buf_e) }));
},
.subnet => |s| {
var buf: [SocketAddress.inet.INET6_ADDRSTRLEN]u8 = @splat(0);
list.appendAssumeCapacity(bun.String.createFormatForJS(globalThis, "Subnet: {s} {s}/{d}", .{ s.network.family().upper(), s.network.fmt(&buf), s.prefix }));
},
}
}
return JSC.JSArray.create(globalThis, list.items);
}
pub fn onStructuredCloneSerialize(this: *@This(), globalThis: *JSC.JSGlobalObject, ctx: *anyopaque, writeBytes: *const fn (*anyopaque, ptr: [*]const u8, len: u32) callconv(JSC.conv) void) void {
_ = globalThis;
this.mutex.lock();
defer this.mutex.unlock();
this.ref();
const writer = StructuredCloneWriter.Writer{ .context = .{ .ctx = ctx, .impl = writeBytes } };
try writer.writeInt(usize, @intFromPtr(this), .little);
}
const StructuredCloneWriter = struct {
ctx: *anyopaque,
impl: *const fn (*anyopaque, ptr: [*]const u8, len: u32) callconv(JSC.conv) void,
pub const Writer = std.io.Writer(@This(), Error, write);
pub const Error = error{};
fn write(this: StructuredCloneWriter, bytes: []const u8) Error!usize {
this.impl(this.ctx, bytes.ptr, @as(u32, @truncate(bytes.len)));
return bytes.len;
}
};
pub fn onStructuredCloneDeserialize(globalThis: *JSC.JSGlobalObject, ptr: [*]u8, end: [*]u8) bun.JSError!JSC.JSValue {
const total_length: usize = @intFromPtr(end) - @intFromPtr(ptr);
var buffer_stream = std.io.fixedBufferStream(ptr[0..total_length]);
const reader = buffer_stream.reader();
const int = reader.readInt(usize, .little) catch return globalThis.throw("BlockList.onStructuredCloneDeserialize failed", .{});
const this: *@This() = @ptrFromInt(int);
return this.toJS(globalThis);
}
pub const Rule = union(enum) {
addr: sockaddr,
range: struct { start: sockaddr, end: sockaddr },
subnet: struct { network: sockaddr, prefix: u8 },
};
fn _compare(l: sockaddr, r: sockaddr) ?std.math.Order {
if (l.as_v4()) |l_4| if (r.as_v4()) |r_4| return std.math.order(@byteSwap((l_4)), @byteSwap((r_4)));
if (l.sin.family == std.posix.AF.INET6 and r.sin.family == std.posix.AF.INET6) return _compare_ipv6(l.sin6, r.sin6);
return null;
}
fn _compare_ipv6(l: sockaddr.in6, r: sockaddr.in6) std.math.Order {
return std.math.order(@byteSwap((@as(u128, @bitCast(l.addr)))), @byteSwap((@as(u128, @bitCast(r.addr)))));
}

View File

@@ -6,6 +6,7 @@ const JSC = bun.JSC;
const string = bun.string;
const Output = bun.Output;
const ZigString = JSC.ZigString;
const validators = @import("./util/validators.zig");
//
//
@@ -67,21 +68,14 @@ pub fn setDefaultAutoSelectFamilyAttemptTimeout(global: *JSC.JSGlobalObject) JSC
return globalThis.throw("missing argument", .{});
}
const arg = arguments.slice()[0];
if (!arg.isInt32AsAnyInt()) {
return globalThis.throwInvalidArguments("autoSelectFamilyAttemptTimeoutDefault", .{});
}
const value: u32 = @max(10, arg.coerceToInt32(globalThis));
autoSelectFamilyAttemptTimeoutDefault = value;
var value = try validators.validateInt32(globalThis, arg, "value", .{}, 1, null);
if (value < 10) value = 10;
autoSelectFamilyAttemptTimeoutDefault = @intCast(value);
return JSC.jsNumber(value);
}
}).setter, 1, .{});
}
pub fn createBinding(global: *JSC.JSGlobalObject) JSC.JSValue {
const SocketAddress = bun.JSC.GeneratedClassesList.SocketAddress;
const net = JSC.JSValue.createEmptyObjectWithNullPrototype(global);
pub const SocketAddress = bun.JSC.Codegen.JSSocketAddress.getConstructor;
net.put(global, "SocketAddress", SocketAddress.js.getConstructor(global));
return net;
}
pub const BlockList = JSC.Codegen.JSBlockList.getConstructor;

View File

@@ -202,7 +202,8 @@ export class ClassDefinition {
configurable?: boolean;
enumerable?: boolean;
structuredClone?: boolean | { transferable: boolean; tag: number };
structuredClone?: { transferable: boolean; tag: number };
customInspect?: boolean;
callbacks?: Record<string, string>;
@@ -245,7 +246,7 @@ export function define(
estimatedSize = false,
call = false,
construct = false,
structuredClone = false,
structuredClone,
...rest
} = {} as Partial<ClassDefinition>,
): ClassDefinition {

View File

@@ -426,6 +426,15 @@ JSC_DECLARE_CUSTOM_GETTER(js${typeName}Constructor);
"onStructuredCloneDeserialize",
)}(JSC::JSGlobalObject*, const uint8_t*, const uint8_t*);` + "\n";
}
if (obj.customInspect) {
externs += `extern JSC_CALLCONV JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${protoSymbolName(typeName, "customInspect")}(JSC::JSGlobalObject*, JSC::CallFrame*);\n`;
specialSymbols += `
this->putDirect(vm, builtinNames(vm).inspectCustomPublicName(), JSFunction::create(vm, globalObject, 2, String("[nodejs.util.inspect.custom]"_s), ${protoSymbolName(
typeName,
"customInspect",
)}, ImplementationVisibility::Public), PropertyAttribute::Function | 0);`;
}
if (obj.finalize) {
externs +=
`extern JSC_CALLCONV void JSC_HOST_CALL_ATTRIBUTES ${classSymbolName(typeName, "finalize")}(void*);` + "\n";
@@ -1778,6 +1787,7 @@ function generateZig(
values = [],
hasPendingActivity = false,
structuredClone = false,
customInspect = false,
getInternalProperties = false,
callbacks = {},
} = {} as ClassDefinition,
@@ -1802,6 +1812,10 @@ function generateZig(
exports.set("onStructuredCloneDeserialize", symbolName(typeName, "onStructuredCloneDeserialize"));
}
if (customInspect) {
exports.set("customInspect", symbolName(typeName, "customInspect"));
}
proto = {
...Object.fromEntries(Object.entries(own || {}).map(([name, getterName]) => [name, { getter: getterName }])),
...proto,
@@ -2059,6 +2073,19 @@ const JavaScriptCoreBindings = struct {
`;
}
if (customInspect) {
// TODO: perhaps exposing this on classes directly isn't the best API choice long term
// it would be better to make a different signature that accepts a writer, then a generated-only function that returns a js string
// the writer function can integrate with our native console.log implementation, the generated function can call the writer version and collect the result
exports.set("customInspect", protoSymbolName(typeName, "customInspect"));
output += `
pub fn ${protoSymbolName(typeName, "customInspect")}(thisValue: *${typeName}, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
if (comptime Environment.enable_logs) JSC.markBinding(@src());
return @call(.always_inline, JSC.toJSHostValue, .{globalObject, @call(.always_inline, ${typeName}.customInspect, .{thisValue, globalObject, callFrame})});
}
`;
}
return (
output.trim() +
`

View File

@@ -12,8 +12,9 @@ const extra_count = NodeErrors.map(x => x.slice(3))
.reduce((ac, cv) => ac + cv.length, 0);
const count = NodeErrors.length + extra_count;
if (count > 65536) {
throw new Error("NodeError count exceeds u16");
if (count > 1 << 16) {
// increase size of the enums below to have more tags
throw new Error(`NodeError can't fit ${count} codes in a u16`);
}
let enumHeader = ``;
@@ -166,9 +167,9 @@ for (const [code, constructor, name, ...other_constructors] of NodeErrors) {
? `${constructor.name} & { name: "${name}", code: "${code}" }`
: `${constructor.name} & { code: "${code}" }`;
dts += `
/**
/**
* Construct an {@link ${constructor.name} ${constructor.name}} with the \`"${code}"\` error code.
*
*
* To override this, update ErrorCode.cpp. To remove this generated type, mention \`"${code}"\` in builtins.d.ts.
*/
declare function $${code}(message: string): ${namedError};\n`;

View File

@@ -1,14 +0,0 @@
const [addServerName, upgradeDuplexToTLS, isNamedPipeSocket, getBufferedAmount] = $zig(
"socket.zig",
"createNodeTLSBinding",
);
const { SocketAddress } = $zig("node_net_binding.zig", "createBinding");
export default {
addServerName,
upgradeDuplexToTLS,
isNamedPipeSocket,
getBufferedAmount,
SocketAddress,
normalizedArgsSymbol: Symbol("normalizedArgs"),
};

View File

@@ -53,7 +53,7 @@ const {
validateAbortSignal,
} = require("internal/validators");
const { isIP } = require("./net");
const { isIP } = require("node:net");
const EventEmitter = require("node:events");

View File

@@ -1,7 +1,7 @@
// Hardcoded module "node:dns"
const dns = Bun.dns;
const utilPromisifyCustomSymbol = Symbol.for("nodejs.util.promisify.custom");
const { isIP } = require("./net");
const { isIP } = require("node:net");
const {
validateFunction,
validateArray,

View File

@@ -22,20 +22,24 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.
const { Duplex } = require("node:stream");
const EventEmitter = require("node:events");
const {
SocketAddress,
addServerName,
upgradeDuplexToTLS,
isNamedPipeSocket,
normalizedArgsSymbol,
getBufferedAmount,
} = require("internal/net");
const [addServerName, upgradeDuplexToTLS, isNamedPipeSocket, getBufferedAmount] = $zig(
"socket.zig",
"createNodeTLSBinding",
);
const normalizedArgsSymbol = Symbol("normalizedArgs");
const { ExceptionWithHostPort } = require("internal/shared");
import type { SocketListener, SocketHandler } from "bun";
import type { ServerOpts } from "node:net";
const { getTimerDuration } = require("internal/timers");
const { validateFunction, validateNumber, validateAbortSignal } = require("internal/validators");
const getDefaultAutoSelectFamily = $zig("node_net_binding.zig", "getDefaultAutoSelectFamily");
const setDefaultAutoSelectFamily = $zig("node_net_binding.zig", "setDefaultAutoSelectFamily");
const getDefaultAutoSelectFamilyAttemptTimeout = $zig("node_net_binding.zig", "getDefaultAutoSelectFamilyAttemptTimeout"); // prettier-ignore
const setDefaultAutoSelectFamilyAttemptTimeout = $zig("node_net_binding.zig", "setDefaultAutoSelectFamilyAttemptTimeout"); // prettier-ignore
const SocketAddress = $zig("node_net_binding.zig", "SocketAddress");
const BlockList = $zig("node_net_binding.zig", "BlockList");
// IPv4 Segment
const v4Seg = "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])";
const v4Str = `(?:${v4Seg}\\.){3}${v4Seg}`;
@@ -1675,17 +1679,6 @@ function toNumber(x) {
return (x = Number(x)) >= 0 ? x : false;
}
// TODO:
class BlockList {
constructor() {}
addSubnet(_net, _prefix, _type) {}
check(_address, _type) {
return false;
}
}
export default {
createServer,
Server,
@@ -1697,10 +1690,10 @@ export default {
Socket,
_normalizeArgs: normalizeArgs,
getDefaultAutoSelectFamily: $zig("node_net_binding.zig", "getDefaultAutoSelectFamily"),
setDefaultAutoSelectFamily: $zig("node_net_binding.zig", "setDefaultAutoSelectFamily"),
getDefaultAutoSelectFamilyAttemptTimeout: $zig("node_net_binding.zig", "getDefaultAutoSelectFamilyAttemptTimeout"),
setDefaultAutoSelectFamilyAttemptTimeout: $zig("node_net_binding.zig", "setDefaultAutoSelectFamilyAttemptTimeout"),
getDefaultAutoSelectFamily,
setDefaultAutoSelectFamily,
getDefaultAutoSelectFamilyAttemptTimeout,
setDefaultAutoSelectFamilyAttemptTimeout,
BlockList,
SocketAddress,

View File

@@ -2,7 +2,7 @@
const { isArrayBufferView, isTypedArray } = require("node:util/types");
const net = require("node:net");
const { Duplex } = require("node:stream");
const { addServerName } = require("internal/net");
const [addServerName] = $zig("socket.zig", "createNodeTLSBinding");
const { throwNotImplemented } = require("internal/shared");
const { throwOnInvalidTLSArray } = require("internal/tls");

View File

@@ -275,7 +275,7 @@ pub fn CowSliceZ(T: type, comptime sentinel: ?T) type {
const cow_str_assertions = Environment.isDebug;
const DebugData = if (cow_str_assertions) struct {
mutex: std.Thread.Mutex = .{},
mutex: bun.Mutex = .{},
allocator: Allocator,
/// number of active borrows
borrows: usize = 0,

View File

@@ -849,6 +849,14 @@ pub const String = extern struct {
return BunString__createUTF8ForJS(globalObject, utf8_slice.ptr, utf8_slice.len);
}
pub fn createFormatForJS(globalObject: *JSC.JSGlobalObject, comptime fmt: [:0]const u8, args: anytype) JSC.JSValue {
JSC.markBinding(@src());
var builder = std.ArrayList(u8).init(bun.default_allocator);
defer builder.deinit();
builder.writer().print(fmt, args) catch bun.outOfMemory();
return BunString__createUTF8ForJS(globalObject, builder.items.ptr, builder.items.len);
}
pub fn parseDate(this: *String, globalObject: *JSC.JSGlobalObject) f64 {
JSC.markBinding(@src());
return Bun__parseDate(globalObject, this);

View File

@@ -19,6 +19,7 @@ const words: Record<string, { reason: string; limit?: number; regex?: boolean }>
"std.StringHashMap(": { reason: "bun.StringHashMap has a faster `eql`" },
"std.enums.tagName(": { reason: "Use bun.tagName instead", limit: 2 },
"std.unicode": { reason: "Use bun.strings instead", limit: 33 },
"std.Thread.Mutex": {reason: "Use bun.Mutex instead", limit: 1 },
"allocator.ptr ==": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" },
"allocator.ptr !=": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior", limit: 1 },

View File

@@ -33,7 +33,7 @@ if (cluster.isPrimary) {
bunRun(joinP(dir, "index.ts"), bunEnv);
});
test("cloneable and non-transferable not-equals", () => {
test("cloneable and non-transferable not-equals (BunFile)", () => {
const dir = tempDirWithFiles("bun-test", {
"index.ts": `
import cluster from "cluster";
@@ -68,3 +68,37 @@ if (cluster.isPrimary) {
});
bunRun(joinP(dir, "index.ts"), bunEnv);
});
test("cloneable and non-transferable not-equals (net.BlockList)", () => {
const dir = tempDirWithFiles("bun-test", {
"index.ts": `
import cluster from "cluster";
import net from "net";
import { expect } from "bun:test";
if (cluster.isPrimary) {
cluster.settings.serialization = "advanced";
const worker = cluster.fork();
const blocklist = new net.BlockList();
console.log("P", "O", blocklist);
blocklist.addAddress("123.123.123.123");
worker.on("online", function () {
worker.send({ blocklist });
});
worker.on("message", function (data) {
worker.kill();
const { blocklist } = data;
console.log("P", "M", blocklist);
expect(blocklist.rules).toBeUndefined();
expect(blocklist).toBeEmptyObject();
process.exit(0);
});
} else {
process.on("message", msg => {
console.log("W", msg);
process.send!(msg);
});
}
`,
});
bunRun(joinP(dir, "index.ts"), bunEnv);
});

View File

@@ -0,0 +1,31 @@
'use strict';
const common = require('../common');
const {
BlockList,
} = require('net');
const {
ok,
notStrictEqual,
} = require('assert');
const blocklist = new BlockList();
blocklist.addAddress('123.123.123.123');
const mc = new MessageChannel();
mc.port1.onmessage = common.mustCall(({ data }) => {
notStrictEqual(data, blocklist);
ok(data.check('123.123.123.123'));
ok(!data.check('123.123.123.124'));
data.addAddress('123.123.123.124');
ok(blocklist.check('123.123.123.124'));
ok(data.check('123.123.123.124'));
mc.port1.close();
});
mc.port2.postMessage(blocklist);

View File

@@ -0,0 +1,284 @@
'use strict';
require('../common');
const {
BlockList,
SocketAddress,
} = require('net');
const assert = require('assert');
const util = require('util');
{
const blockList = new BlockList();
[1, [], {}, null, 1n, undefined, null].forEach((i) => {
assert.throws(() => blockList.addAddress(i), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
[1, [], {}, null, 1n, null].forEach((i) => {
assert.throws(() => blockList.addAddress('1.1.1.1', i), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
assert.throws(() => blockList.addAddress('1.1.1.1', 'foo'), {
code: 'ERR_INVALID_ARG_VALUE'
});
[1, [], {}, null, 1n, undefined, null].forEach((i) => {
assert.throws(() => blockList.addRange(i), {
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => blockList.addRange('1.1.1.1', i), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
[1, [], {}, null, 1n, null].forEach((i) => {
assert.throws(() => blockList.addRange('1.1.1.1', '1.1.1.2', i), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
assert.throws(() => blockList.addRange('1.1.1.1', '1.1.1.2', 'foo'), {
code: 'ERR_INVALID_ARG_VALUE'
});
}
{
const blockList = new BlockList();
blockList.addAddress('1.1.1.1');
blockList.addAddress('8592:757c:efae:4e45:fb5d:d62a:0d00:8e17', 'ipv6');
blockList.addAddress('::ffff:1.1.1.2', 'ipv6');
assert(blockList.check('1.1.1.1'));
assert(!blockList.check('1.1.1.1', 'ipv6'));
assert(!blockList.check('8592:757c:efae:4e45:fb5d:d62a:0d00:8e17'));
assert(blockList.check('8592:757c:efae:4e45:fb5d:d62a:0d00:8e17', 'ipv6'));
assert(blockList.check('::ffff:1.1.1.1', 'ipv6'));
assert(blockList.check('::ffff:1.1.1.1', 'IPV6'));
assert(blockList.check('1.1.1.2'));
assert(!blockList.check('1.2.3.4'));
assert(!blockList.check('::1', 'ipv6'));
}
{
const blockList = new BlockList();
const sa1 = new SocketAddress({ address: '1.1.1.1' });
const sa2 = new SocketAddress({
address: '8592:757c:efae:4e45:fb5d:d62a:0d00:8e17',
family: 'ipv6'
});
const sa3 = new SocketAddress({ address: '1.1.1.2' });
blockList.addAddress(sa1);
blockList.addAddress(sa2);
blockList.addAddress('::ffff:1.1.1.2', 'ipv6');
assert(blockList.check('1.1.1.1'));
assert(blockList.check(sa1));
assert(!blockList.check('1.1.1.1', 'ipv6'));
assert(!blockList.check('8592:757c:efae:4e45:fb5d:d62a:0d00:8e17'));
assert(blockList.check('8592:757c:efae:4e45:fb5d:d62a:0d00:8e17', 'ipv6'));
assert(blockList.check(sa2));
assert(blockList.check('::ffff:1.1.1.1', 'ipv6'));
assert(blockList.check('::ffff:1.1.1.1', 'IPV6'));
assert(blockList.check('1.1.1.2'));
assert(blockList.check(sa3));
assert(!blockList.check('1.2.3.4'));
assert(!blockList.check('::1', 'ipv6'));
}
{
const blockList = new BlockList();
blockList.addRange('1.1.1.1', '1.1.1.10');
blockList.addRange('::1', '::f', 'ipv6');
assert(!blockList.check('1.1.1.0'));
for (let n = 1; n <= 10; n++)
assert(blockList.check(`1.1.1.${n}`));
assert(!blockList.check('1.1.1.11'));
assert(!blockList.check('::0', 'ipv6'));
for (let n = 0x1; n <= 0xf; n++) {
assert(blockList.check(`::${n.toString(16)}`, 'ipv6'),
`::${n.toString(16)} check failed`);
}
assert(!blockList.check('::10', 'ipv6'));
}
{
const blockList = new BlockList();
const sa1 = new SocketAddress({ address: '1.1.1.1' });
const sa2 = new SocketAddress({ address: '1.1.1.10' });
const sa3 = new SocketAddress({ address: '::1', family: 'ipv6' });
const sa4 = new SocketAddress({ address: '::f', family: 'ipv6' });
blockList.addRange(sa1, sa2);
blockList.addRange(sa3, sa4);
assert(!blockList.check('1.1.1.0'));
for (let n = 1; n <= 10; n++)
assert(blockList.check(`1.1.1.${n}`));
assert(!blockList.check('1.1.1.11'));
assert(!blockList.check('::0', 'ipv6'));
for (let n = 0x1; n <= 0xf; n++) {
assert(blockList.check(`::${n.toString(16)}`, 'ipv6'),
`::${n.toString(16)} check failed`);
}
assert(!blockList.check('::10', 'ipv6'));
}
{
const blockList = new BlockList();
blockList.addSubnet('1.1.1.0', 16);
blockList.addSubnet('8592:757c:efae:4e45::', 64, 'ipv6');
assert(blockList.check('1.1.0.1'));
assert(blockList.check('1.1.1.1'));
assert(!blockList.check('1.2.0.1'));
assert(blockList.check('::ffff:1.1.0.1', 'ipv6'));
assert(blockList.check('8592:757c:efae:4e45:f::', 'ipv6'));
assert(blockList.check('8592:757c:efae:4e45::f', 'ipv6'));
assert(!blockList.check('8592:757c:efae:4f45::f', 'ipv6'));
}
{
const blockList = new BlockList();
const sa1 = new SocketAddress({ address: '1.1.1.0' });
const sa2 = new SocketAddress({ address: '1.1.1.1' });
blockList.addSubnet(sa1, 16);
blockList.addSubnet('8592:757c:efae:4e45::', 64, 'ipv6');
assert(blockList.check('1.1.0.1'));
assert(blockList.check(sa2));
assert(!blockList.check('1.2.0.1'));
assert(blockList.check('::ffff:1.1.0.1', 'ipv6'));
assert(blockList.check('8592:757c:efae:4e45:f::', 'ipv6'));
assert(blockList.check('8592:757c:efae:4e45::f', 'ipv6'));
assert(!blockList.check('8592:757c:efae:4f45::f', 'ipv6'));
}
{
const blockList = new BlockList();
blockList.addAddress('1.1.1.1');
blockList.addRange('10.0.0.1', '10.0.0.10');
blockList.addSubnet('8592:757c:efae:4e45::', 64, 'IpV6'); // Case insensitive
const rulesCheck = [
'Subnet: IPv6 8592:757c:efae:4e45::/64',
'Range: IPv4 10.0.0.1-10.0.0.10',
'Address: IPv4 1.1.1.1',
];
assert.deepStrictEqual(blockList.rules, rulesCheck);
assert(blockList.check('1.1.1.1'));
assert(blockList.check('10.0.0.5'));
assert(blockList.check('::ffff:10.0.0.5', 'ipv6'));
assert(blockList.check('8592:757c:efae:4e45::f', 'ipv6'));
assert(!blockList.check('123.123.123.123'));
assert(!blockList.check('8592:757c:efaf:4e45:fb5d:d62a:0d00:8e17', 'ipv6'));
assert(!blockList.check('::ffff:123.123.123.123', 'ipv6'));
}
{
// This test validates boundaries of non-aligned CIDR bit prefixes
const blockList = new BlockList();
blockList.addSubnet('10.0.0.0', 27);
blockList.addSubnet('8592:757c:efaf::', 51, 'ipv6');
for (let n = 0; n <= 31; n++)
assert(blockList.check(`10.0.0.${n}`));
assert(!blockList.check('10.0.0.32'));
assert(blockList.check('8592:757c:efaf:0:0:0:0:0', 'ipv6'));
assert(blockList.check('8592:757c:efaf:1fff:ffff:ffff:ffff:ffff', 'ipv6'));
assert(!blockList.check('8592:757c:efaf:2fff:ffff:ffff:ffff:ffff', 'ipv6'));
}
{
// Regression test for https://github.com/nodejs/node/issues/39074
const blockList = new BlockList();
blockList.addRange('10.0.0.2', '10.0.0.10');
// IPv4 checks against IPv4 range.
assert(blockList.check('10.0.0.2'));
assert(blockList.check('10.0.0.10'));
assert(!blockList.check('192.168.0.3'));
assert(!blockList.check('2.2.2.2'));
assert(!blockList.check('255.255.255.255'));
// IPv6 checks against IPv4 range.
assert(blockList.check('::ffff:0a00:0002', 'ipv6'));
assert(blockList.check('::ffff:0a00:000a', 'ipv6'));
assert(!blockList.check('::ffff:c0a8:0003', 'ipv6'));
assert(!blockList.check('::ffff:0202:0202', 'ipv6'));
assert(!blockList.check('::ffff:ffff:ffff', 'ipv6'));
}
{
const blockList = new BlockList();
assert.throws(() => blockList.addRange('1.1.1.2', '1.1.1.1'), /ERR_INVALID_ARG_VALUE/);
}
{
const blockList = new BlockList();
assert.throws(() => blockList.addSubnet(1), /ERR_INVALID_ARG_TYPE/);
assert.throws(() => blockList.addSubnet('1.1.1.1', ''), /ERR_INVALID_ARG_TYPE/);
assert.throws(() => blockList.addSubnet('1.1.1.1', NaN), /ERR_OUT_OF_RANGE/);
assert.throws(() => blockList.addSubnet('', 1, 1), /ERR_INVALID_ARG_TYPE/);
assert.throws(() => blockList.addSubnet('', 1, ''), /ERR_INVALID_ARG_VALUE/);
assert.throws(() => blockList.addSubnet('1.1.1.1', -1, 'ipv4'), /ERR_OUT_OF_RANGE/);
assert.throws(() => blockList.addSubnet('1.1.1.1', 33, 'ipv4'), /ERR_OUT_OF_RANGE/);
assert.throws(() => blockList.addSubnet('::', -1, 'ipv6'), /ERR_OUT_OF_RANGE/);
assert.throws(() => blockList.addSubnet('::', 129, 'ipv6'), /ERR_OUT_OF_RANGE/);
}
{
const blockList = new BlockList();
assert.throws(() => blockList.check(1), /ERR_INVALID_ARG_TYPE/);
assert.throws(() => blockList.check('', 1), /ERR_INVALID_ARG_TYPE/);
}
// {
// const blockList = new BlockList();
// const ret = util.inspect(blockList, { depth: -1 });
// assert.strictEqual(ret, '[BlockList]');
// }
// {
// const blockList = new BlockList();
// const ret = util.inspect(blockList, { depth: null });
// assert(ret.includes('rules: []'));
// }
{
// Test for https://github.com/nodejs/node/issues/43360
const blocklist = new BlockList();
blocklist.addSubnet('1.1.1.1', 32, 'ipv4');
assert(blocklist.check('1.1.1.1'));
assert(!blocklist.check('1.1.1.2'));
assert(!blocklist.check('2.3.4.5'));
}
{
assert(BlockList.isBlockList(new BlockList()));
assert(!BlockList.isBlockList({}));
}

View File

@@ -277,8 +277,7 @@ test("cloneable and transferable equals", async () => {
await promise;
});
test("cloneable and non-transferable equals", async () => {
const assert = require("assert");
test("cloneable and non-transferable equals (BunFile)", async () => {
const mc = new MessageChannel();
const file = Bun.file(import.meta.filename);
expect(file).toBeInstanceOf(Blob); // Bun.BunFile isnt exposed to JS
@@ -300,3 +299,27 @@ test("cloneable and non-transferable equals", async () => {
mc.port2.postMessage(file);
await promise;
});
test("cloneable and non-transferable equals (net.BlockList)", async () => {
const net = require("node:net");
const mc = new MessageChannel();
const blocklist = new net.BlockList();
blocklist.addAddress("123.123.123.123");
const { promise, resolve, reject } = Promise.withResolvers();
mc.port1.onmessage = ({ data }) => {
try {
expect(data).toBeInstanceOf(net.BlockList);
expect(data.check("123.123.123.123")).toBeTrue();
expect(!data.check("123.123.123.124")).toBeTrue();
data.addAddress("123.123.123.124");
expect(blocklist.check("123.123.123.124")).toBeTrue();
expect(data.check("123.123.123.124")).toBeTrue();
mc.port1.close();
resolve();
} catch (e) {
reject(e);
}
};
mc.port2.postMessage(blocklist);
await promise;
});

View File

@@ -205,6 +205,20 @@ describe("structured clone", () => {
});
});
describe("net.BlockList works", () => {
test("simple", () => {
const net = require("node:net");
const blocklist = new net.BlockList();
blocklist.addAddress("123.123.123.123");
const newlist = structuredClone(blocklist);
expect(newlist.check("123.123.123.123")).toBeTrue();
expect(!newlist.check("123.123.123.124")).toBeTrue();
newlist.addAddress("123.123.123.124");
expect(blocklist.check("123.123.123.124")).toBeTrue();
expect(newlist.check("123.123.123.124")).toBeTrue();
});
});
describe("transferables", () => {
test("ArrayBuffer", () => {
const buffer = Uint8Array.from([1]).buffer;