mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
node:net: implement BlockList (#19277)
This commit is contained in:
@@ -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");
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
240
src/bun.js/node/net/BlockList.zig
Normal file
240
src/bun.js/node/net/BlockList.zig
Normal 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)))));
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() +
|
||||
`
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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"),
|
||||
};
|
||||
@@ -53,7 +53,7 @@ const {
|
||||
validateAbortSignal,
|
||||
} = require("internal/validators");
|
||||
|
||||
const { isIP } = require("./net");
|
||||
const { isIP } = require("node:net");
|
||||
|
||||
const EventEmitter = require("node:events");
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
31
test/js/node/test/parallel/test-blocklist-clone.js
Normal file
31
test/js/node/test/parallel/test-blocklist-clone.js
Normal 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);
|
||||
284
test/js/node/test/parallel/test-blocklist.js
Normal file
284
test/js/node/test/parallel/test-blocklist.js
Normal 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({}));
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user