diff options
author | Satya Rohith <me@satyarohith.com> | 2024-06-18 16:16:13 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-18 10:46:13 +0000 |
commit | 8c4b33db0d05181a0e5538bddaf063144724c938 (patch) | |
tree | 368ced63a6ac484db822212d9d332d92bd3466ed /ext/node/polyfills | |
parent | 4b83ce8acabaf868d47bf764fce18ce5450fd314 (diff) |
feat(ext/node): add BlockList & SocketAddress classes (#24229)
Closes https://github.com/denoland/deno/issues/24059
Diffstat (limited to 'ext/node/polyfills')
-rw-r--r-- | ext/node/polyfills/internal/blocklist.mjs | 227 | ||||
-rw-r--r-- | ext/node/polyfills/internal/errors.ts | 14 | ||||
-rw-r--r-- | ext/node/polyfills/net.ts | 6 |
3 files changed, 234 insertions, 13 deletions
diff --git a/ext/node/polyfills/internal/blocklist.mjs b/ext/node/polyfills/internal/blocklist.mjs new file mode 100644 index 000000000..a9aba03b6 --- /dev/null +++ b/ext/node/polyfills/internal/blocklist.mjs @@ -0,0 +1,227 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { primordials } from "ext:core/mod.js"; +import { + op_blocklist_add_address, + op_blocklist_add_range, + op_blocklist_add_subnet, + op_blocklist_check, + op_blocklist_new, + op_socket_address_get_serialization, + op_socket_address_parse, +} from "ext:core/ops"; + +import { + validateInt32, + validateObject, + validatePort, + validateString, + validateUint32, +} from "ext:deno_node/internal/validators.mjs"; +import { ERR_INVALID_ARG_VALUE } from "ext:deno_node/internal/errors.ts"; +import { customInspectSymbol } from "ext:deno_node/internal/util.mjs"; +import { inspect } from "ext:deno_node/internal/util/inspect.mjs"; + +const { Symbol } = primordials; + +const internalBlockList = Symbol("blocklist"); + +class BlockList { + constructor() { + this[internalBlockList] = op_blocklist_new(); + } + + [customInspectSymbol](depth, options) { + if (depth < 0) { + return this; + } + + const opts = { + ...options, + depth: options.depth == null ? null : options.depth - 1, + }; + + return `BlockList ${ + inspect({ + rules: [], // TODO(satyarohith): provide the actual rules + }, opts) + }`; + } + + addAddress(address, family = "ipv4") { + if (!SocketAddress.isSocketAddress(address)) { + validateString(address, "address"); + validateString(family, "family"); + new SocketAddress({ + address, + family, + }); + } else { + address = address.address; + } + op_blocklist_add_address(this[internalBlockList], address); + } + + addRange(start, end, family = "ipv4") { + if (!SocketAddress.isSocketAddress(start)) { + validateString(start, "start"); + validateString(family, "family"); + new SocketAddress({ + address: start, + family, + }); + } else { + start = start.address; + } + if (!SocketAddress.isSocketAddress(end)) { + validateString(end, "end"); + validateString(family, "family"); + new SocketAddress({ + address: end, + family, + }); + } else { + end = end.address; + } + const ret = op_blocklist_add_range(this[internalBlockList], start, end); + if (ret === false) { + throw new ERR_INVALID_ARG_VALUE("start", start, "must come before end"); + } + } + + addSubnet(network, prefix, family = "ipv4") { + if (!SocketAddress.isSocketAddress(network)) { + validateString(network, "network"); + validateString(family, "family"); + new SocketAddress({ + address: network, + family, + }); + } else { + network = network.address; + family = network.family; + } + switch (family) { + case "ipv4": + validateInt32(prefix, "prefix", 0, 32); + break; + case "ipv6": + validateInt32(prefix, "prefix", 0, 128); + break; + } + op_blocklist_add_subnet(this[internalBlockList], network, prefix); + } + + check(address, family = "ipv4") { + if (!SocketAddress.isSocketAddress(address)) { + validateString(address, "address"); + validateString(family, "family"); + try { + new SocketAddress({ + address, + family, + }); + } catch { + // Ignore the error. If it's not a valid address, return false. + return false; + } + } else { + family = address.family; + address = address.address; + } + try { + return op_blocklist_check(this[internalBlockList], address, family); + } catch (_) { + // Node API expects false as return value if the address is invalid. + // Example: `blocklist.check("1.1.1.1", "ipv6")` should return false. + return false; + } + } + + get rules() { + // TODO(satyarohith): return the actual rules + return []; + } +} + +const kDetail = Symbol("kDetail"); + +class SocketAddress { + static isSocketAddress(value) { + return value?.[kDetail] !== undefined; + } + + constructor(options = kEmptyObject) { + validateObject(options, "options"); + let { family = "ipv4" } = options; + const { + address = (family === "ipv4" ? "127.0.0.1" : "::"), + port = 0, + flowlabel = 0, + } = options; + + if (typeof family?.toLowerCase === "function") { + // deno-lint-ignore prefer-primordials + family = family.toLowerCase(); + } + switch (family) { + case "ipv4": + break; + case "ipv6": + break; + default: + throw new ERR_INVALID_ARG_VALUE("options.family", options.family); + } + + validateString(address, "options.address"); + validatePort(port, "options.port"); + validateUint32(flowlabel, "options.flowlabel", false); + + this[kDetail] = { + address, + port, + family, + flowlabel, + }; + const useInput = op_socket_address_parse( + address, + port, + family, + ); + if (!useInput) { + const { 0: address_, 1: family_ } = op_socket_address_get_serialization(); + this[kDetail].address = address_; + this[kDetail].family = family_; + } + } + + get address() { + return this[kDetail].address; + } + + get port() { + return this[kDetail].port; + } + + get family() { + return this[kDetail].family; + } + + get flowlabel() { + // TODO(satyarohith): Implement this in Rust. + // The flow label can be changed internally. + return this[kDetail].flowlabel; + } + + toJSON() { + return { + address: this.address, + port: this.port, + family: this.family, + flowlabel: this.flowlabel, + }; + } +} + +export { BlockList, SocketAddress }; diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts index cb4119411..6529e9894 100644 --- a/ext/node/polyfills/internal/errors.ts +++ b/ext/node/polyfills/internal/errors.ts @@ -667,9 +667,7 @@ function invalidArgTypeHelper(input: any) { return ` Received type ${typeof input} (${inspected})`; } -export class ERR_OUT_OF_RANGE extends RangeError { - code = "ERR_OUT_OF_RANGE"; - +export class ERR_OUT_OF_RANGE extends NodeRangeError { constructor( str: string, range: string, @@ -694,15 +692,7 @@ export class ERR_OUT_OF_RANGE extends RangeError { } msg += ` It must be ${range}. Received ${received}`; - super(msg); - - const { name } = this; - // Add the error code to the name to include it in the stack trace. - this.name = `${name} [${this.code}]`; - // Access the stack to generate the error message including the error code from the name. - this.stack; - // Reset the name to the actual name. - this.name = name; + super("ERR_OUT_OF_RANGE", msg); } } diff --git a/ext/node/polyfills/net.ts b/ext/node/polyfills/net.ts index 66b7735d9..6625ce7b5 100644 --- a/ext/node/polyfills/net.ts +++ b/ext/node/polyfills/net.ts @@ -24,6 +24,8 @@ // deno-lint-ignore-file prefer-primordials import { notImplemented } from "ext:deno_node/_utils.ts"; +import { BlockList, SocketAddress } from "ext:deno_node/internal/blocklist.mjs"; + import { EventEmitter } from "node:events"; import { isIP, @@ -2472,7 +2474,7 @@ export function createServer( return new Server(options, connectionListener); } -export { isIP, isIPv4, isIPv6 }; +export { BlockList, isIP, isIPv4, isIPv6, SocketAddress }; export default { _createServerHandle, @@ -2480,6 +2482,8 @@ export default { isIP, isIPv4, isIPv6, + BlockList, + SocketAddress, connect, createConnection, createServer, |