From b98f1a6b4f1ab5671c66fdd7c2a2d0ebe9a05435 Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 00:27:11 +0900 Subject: [PATCH 01/12] add matchSubnets --- net/unstable_ip.ts | 239 ++++++++++++++++++++++++++++++++++++++-- net/unstable_ip_test.ts | 92 +++++++++++++++- 2 files changed, 322 insertions(+), 9 deletions(-) diff --git a/net/unstable_ip.ts b/net/unstable_ip.ts index d1a8025bdc87..064e281f258c 100644 --- a/net/unstable_ip.ts +++ b/net/unstable_ip.ts @@ -23,10 +23,13 @@ export function isIPv4(addr: string): boolean { const octets = addr.split("."); - return octets.length === 4 && octets.every((octet) => { - const n = Number(octet); - return n >= 0 && n <= 255 && !isNaN(n); - }); + return ( + octets.length === 4 && + octets.every((octet) => { + const n = Number(octet); + return n >= 0 && n <= 255 && !isNaN(n); + }) + ); } /** @@ -75,8 +78,228 @@ export function isIPv6(addr: string): boolean { hextets.splice(idx, 0, ""); } - return hextets.length === 8 && hextets.every((hextet) => { - const n = hextet === "" ? 0 : parseInt(hextet, 16); - return n >= 0 && n <= 65535 && !isNaN(n); - }); + return ( + hextets.length === 8 && + hextets.every((hextet) => { + const n = hextet === "" ? 0 : parseInt(hextet, 16); + return n >= 0 && n <= 65535 && !isNaN(n); + }) + ); +} + +/** + * Checks if an IP address matches a subnet or specific IP address. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @param ip The IP address to check (IPv4 or IPv6) + * @param subnetOrIps The subnet in CIDR notation (e.g., "192.168.1.0/24") or a specific IP address + * @returns true if the IP address matches the subnet or IP, false otherwise + * @example Check if the address is a IPv6 + * + * ```ts + * import { matchSubnets } from "@std/net/unstable-ip" + * import { assert } from "@std/assert" + * + * assertEquals(matchSubnets("192.168.1.10", ["192.168.1.0/24"]), true); + * assertEquals(matchSubnets("192.168.2.10", ["192.168.1.0/24"]), false); + * + * assertEquals(matchSubnets("2001:db8::ffff", ["2001:db8::/64"]), true); + * assertEquals(matchSubnets("2001:db9::1", ["2001:db8::/64"]), false); + * ``` + */ +export function matchSubnets(addr: string, subnetOrIps: string[]): boolean { + if (!isValidIP(addr)) { + return false; + } + + for (const subnetOrIp of subnetOrIps) { + if (matchSubnet(addr, subnetOrIp)) { + return true; + } + } + + return false; +} + +function matchSubnet(addr: string, subnet: string): boolean { + // If the subnet doesn't contain "/", treat it as a specific IP address + if (!subnet.includes("/")) { + return addr === subnet; + } + + // Parse subnet into IP address and prefix length + const [subnetIP, prefixLengthStr] = subnet.split("/"); + if (subnetIP === undefined || prefixLengthStr === undefined) { + return false; + } + + // Validate that the subnet IP is a valid IPv4 or IPv6 address + if (!isValidIP(subnetIP)) { + return false; + } + + // Parse and validate the prefix length + const prefix = parseInt(prefixLengthStr, 10); + if (isNaN(prefix)) { + return false; + } + + // Check if both IP and subnet are the same type (IPv4 or IPv6) + const ipIsV4 = isIPv4(addr); + const subnetIsV4 = isIPv4(subnetIP); + + // IP and subnet must be the same version (both IPv4 or both IPv6) + if (ipIsV4 !== subnetIsV4) { + return false; + } + + // Delegate to the appropriate subnet matching function + if (ipIsV4) { + return matchIPv4Subnet(addr, subnetIP, prefix); + } else { + return matchIPv6Subnet(addr, subnetIP, prefix); + } +} + +function isValidIP(ip: string): boolean { + return isIPv4(ip) || isIPv6(ip); +} + +function matchIPv4Subnet( + ip: string, + subnetIP: string, + prefixLength: number +): boolean { + if (prefixLength < 0 || prefixLength > 32) { + return false; + } + + // Special case: /0 matches all IPv4 addresses + if (prefixLength === 0) { + return true; + } + + const ipBytes = ip.split(".").map(Number); + const subnetBytes = subnetIP.split(".").map(Number); + + if (ipBytes.length !== 4 || subnetBytes.length !== 4) { + return false; + } + + const mask = (0xffffffff << (32 - prefixLength)) >>> 0; + + const ipInt = + (ipBytes[0]! << 24) | + (ipBytes[1]! << 16) | + (ipBytes[2]! << 8) | + ipBytes[3]!; + const subnetInt = + (subnetBytes[0]! << 24) | + (subnetBytes[1]! << 16) | + (subnetBytes[2]! << 8) | + subnetBytes[3]!; + + return ((ipInt >>> 0) & mask) === ((subnetInt >>> 0) & mask); +} + +function matchIPv6Subnet( + ip: string, + subnetIP: string, + prefixLength: number +): boolean { + if (prefixLength < 0 || prefixLength > 128) { + return false; + } + + if (prefixLength === 0) { + return true; + } + + const ipExpanded = expandIPv6(ip); + const subnetExpanded = expandIPv6(subnetIP); + + if (!ipExpanded || !subnetExpanded) { + return false; + } + + const ipBytes = ipv6ToBytes(ipExpanded); + const subnetBytes = ipv6ToBytes(subnetExpanded); + + const fullBytes = Math.floor(prefixLength / 8); + const remainingBits = prefixLength % 8; + + for (let i = 0; i < fullBytes; i++) { + if (ipBytes[i] !== subnetBytes[i]) { + return false; + } + } + + if (remainingBits > 0 && fullBytes < 16) { + const mask = 0xff << (8 - remainingBits); + const ipByte = ipBytes[fullBytes]; + const subnetByte = subnetBytes[fullBytes]; + if (ipByte === undefined || subnetByte === undefined) { + return false; + } + return (ipByte & mask) === (subnetByte & mask); + } + + return true; +} + +function expandIPv6(ip: string): string | null { + if (!isIPv6(ip)) { + return null; + } + + if (ip.includes(".")) { + const parts = ip.split(":"); + const ipv4Part = parts.pop(); + if (!ipv4Part) { + return null; + } + const ipv4Bytes = ipv4Part.split(".").map(Number); + if (ipv4Bytes.length !== 4) { + return null; + } + const ipv4Hex = + ((ipv4Bytes[0]! << 8) | ipv4Bytes[1]!).toString(16).padStart(4, "0") + + ":" + + ((ipv4Bytes[2]! << 8) | ipv4Bytes[3]!).toString(16).padStart(4, "0"); + ip = parts.join(":") + ":" + ipv4Hex; + } + + let expanded = ip; + + // Handle :: + if (expanded.includes("::")) { + const parts = expanded.split("::"); + const leftParts = parts[0] ? parts[0].split(":") : []; + const rightParts = parts[1] ? parts[1].split(":") : []; + const missingParts = 8 - leftParts.length - rightParts.length; + + expanded = leftParts + .concat(new Array(missingParts).fill("0")) + .concat(rightParts) + .join(":"); + } + + // Pad each hextet to 4 digits + return expanded + .split(":") + .map((hextet) => hextet.padStart(4, "0")) + .join(":"); +} + +function ipv6ToBytes(expandedIPv6: string): number[] { + const hextets = expandedIPv6.split(":"); + const bytes: number[] = []; + + for (const hextet of hextets) { + const value = parseInt(hextet, 16); + bytes.push((value >> 8) & 0xff, value & 0xff); + } + + return bytes; } diff --git a/net/unstable_ip_test.ts b/net/unstable_ip_test.ts index 1e60342e855d..890bbc894e85 100644 --- a/net/unstable_ip_test.ts +++ b/net/unstable_ip_test.ts @@ -1,6 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. -import { isIPv4, isIPv6 } from "./unstable_ip.ts"; +import { isIPv4, isIPv6, matchSubnets } from "./unstable_ip.ts"; import { assertEquals } from "@std/assert"; Deno.test("isIPv4()", () => { @@ -46,3 +46,93 @@ Deno.test("isIPv6()", () => { assertEquals(isIPv6(addr), expected); } }); + +Deno.test("matchSubnets - IPv4 exact match", () => { + assertEquals(matchSubnets("192.168.1.10", ["192.168.1.10"]), true); + assertEquals(matchSubnets("192.168.1.10", ["192.168.1.11"]), false); +}); + +Deno.test("matchSubnets - IPv4 subnet match", () => { + // Test /24 subnet + assertEquals(matchSubnets("192.168.1.10", ["192.168.1.0/24"]), true); + assertEquals(matchSubnets("192.168.1.255", ["192.168.1.0/24"]), true); + assertEquals(matchSubnets("192.168.2.10", ["192.168.1.0/24"]), false); + + // Test /16 subnet + assertEquals(matchSubnets("192.168.100.10", ["192.168.0.0/16"]), true); + assertEquals(matchSubnets("192.169.1.10", ["192.168.0.0/16"]), false); + + // Test /8 subnet + assertEquals(matchSubnets("192.100.100.100", ["192.0.0.0/8"]), true); + assertEquals(matchSubnets("193.1.1.1", ["192.0.0.0/8"]), false); +}); + +Deno.test("matchSubnets - IPv6 exact match", () => { + assertEquals(matchSubnets("2001:db8::1", ["2001:db8::1"]), true); + assertEquals(matchSubnets("2001:db8::1", ["2001:db8::2"]), false); + assertEquals(matchSubnets("::1", ["::1"]), true); +}); + +Deno.test("matchSubnets - IPv6 subnet match", () => { + // Test /64 subnet + assertEquals(matchSubnets("2001:db8::1", ["2001:db8::/64"]), true); + assertEquals(matchSubnets("2001:db8::ffff", ["2001:db8::/64"]), true); + assertEquals(matchSubnets("2001:db9::1", ["2001:db8::/64"]), false); + + // Test /32 subnet + assertEquals(matchSubnets("2001:db8:1234::1", ["2001:db8::/32"]), true); + assertEquals(matchSubnets("2001:db9::1", ["2001:db8::/32"]), false); + + // Test /128 (exact match) + assertEquals(matchSubnets("2001:db8::1", ["2001:db8::1/128"]), true); + assertEquals(matchSubnets("2001:db8::2", ["2001:db8::1/128"]), false); +}); + +Deno.test("matchSubnets - multiple subnets", () => { + const subnets = [ + "192.168.1.0/24", + "10.0.0.0/8", + "2001:db8::/32", + "172.16.0.100", // exact IP + ]; + + assertEquals(matchSubnets("192.168.1.50", subnets), true); + assertEquals(matchSubnets("10.5.5.5", subnets), true); + assertEquals(matchSubnets("2001:db8:1234::1", subnets), true); + assertEquals(matchSubnets("172.16.0.100", subnets), true); + assertEquals(matchSubnets("172.16.0.101", subnets), false); + assertEquals(matchSubnets("8.8.8.8", subnets), false); +}); + +Deno.test("matchSubnets - mixed IPv4 and IPv6", () => { + const subnets = ["192.168.1.0/24", "2001:db8::/64"]; + + assertEquals(matchSubnets("192.168.1.10", subnets), true); + assertEquals(matchSubnets("2001:db8::1", subnets), true); + assertEquals(matchSubnets("10.0.0.1", subnets), false); + assertEquals(matchSubnets("2001:db9::1", subnets), false); +}); + +Deno.test("matchSubnets - invalid inputs", () => { + assertEquals(matchSubnets("invalid-ip", ["192.168.1.0/24"]), false); + assertEquals(matchSubnets("192.168.1.10", ["invalid-subnet"]), false); + assertEquals(matchSubnets("192.168.1.10", ["192.168.1.0/33"]), false); + assertEquals(matchSubnets("2001:db8::1", ["2001:db8::/129"]), false); +}); + +Deno.test("matchSubnets - edge cases", () => { + // Empty subnets array + assertEquals(matchSubnets("192.168.1.10", []), false); + + // /0 subnet (all IPs) + assertEquals(matchSubnets("192.168.1.10", ["0.0.0.0/0"]), true); + assertEquals(matchSubnets("8.8.8.8", ["0.0.0.0/0"]), true); + + // IPv6 /0 subnet + assertEquals(matchSubnets("2001:db8::1", ["::/0"]), true); + assertEquals(matchSubnets("fe80::1", ["::/0"]), true); + + // /32 for IPv4 (exact match) + assertEquals(matchSubnets("192.168.1.10", ["192.168.1.10/32"]), true); + assertEquals(matchSubnets("192.168.1.11", ["192.168.1.10/32"]), false); +}); From 5c8fe3cc030a4811ba0176ccd954f30eba55c7d4 Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 00:34:22 +0900 Subject: [PATCH 02/12] update --- http/file_server.ts | 60 ++++++++++++++++++++++----------------------- net/unstable_ip.ts | 22 ++++++----------- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/http/file_server.ts b/http/file_server.ts index 135ae046df7c..7a2ed599025c 100644 --- a/http/file_server.ts +++ b/http/file_server.ts @@ -518,19 +518,19 @@ function dirViewerTemplate(dirname: string, entries: EntryInfo[]): string {

Index of ${headerPaths - .map((path, index) => { - if (path === "") return ""; - const depth = headerPaths.length - index - 1; - let link; - if (depth == 0) { - link = "."; - } else { - link = "../".repeat(depth); - } - // deno-fmt-ignore - return html`${escape(path)}`; - }) - .join("/")}/ + .map((path, index) => { + if (path === "") return ""; + const depth = headerPaths.length - index - 1; + let link; + if (depth == 0) { + link = "."; + } else { + link = "../".repeat(depth); + } + // deno-fmt-ignore + return html`${escape(path)}`; + }) + .join("/")}/

@@ -541,23 +541,23 @@ function dirViewerTemplate(dirname: string, entries: EntryInfo[]): string { ${entries - .map( - (entry) => - html` - - - - - - `, - ) - .join("")} + .map( + (entry) => + html` + + + + + + `, + ) + .join("")}
- ${entry.mode} - - ${entry.size} - - ${escape(entry.name)} -
+ ${entry.mode} + + ${entry.size} + + ${escape(entry.name)} +
diff --git a/net/unstable_ip.ts b/net/unstable_ip.ts index 064e281f258c..7cdcb8dd4cba 100644 --- a/net/unstable_ip.ts +++ b/net/unstable_ip.ts @@ -23,13 +23,10 @@ export function isIPv4(addr: string): boolean { const octets = addr.split("."); - return ( - octets.length === 4 && - octets.every((octet) => { - const n = Number(octet); - return n >= 0 && n <= 255 && !isNaN(n); - }) - ); + return octets.length === 4 && octets.every((octet) => { + const n = Number(octet); + return n >= 0 && n <= 255 && !isNaN(n); + }); } /** @@ -78,13 +75,10 @@ export function isIPv6(addr: string): boolean { hextets.splice(idx, 0, ""); } - return ( - hextets.length === 8 && - hextets.every((hextet) => { - const n = hextet === "" ? 0 : parseInt(hextet, 16); - return n >= 0 && n <= 65535 && !isNaN(n); - }) - ); + return hextets.length === 8 && hextets.every((hextet) => { + const n = hextet === "" ? 0 : parseInt(hextet, 16); + return n >= 0 && n <= 65535 && !isNaN(n); + }); } /** From 57c0479be64d1b9a8f52c7750c2ac6fd7902ce57 Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 00:38:06 +0900 Subject: [PATCH 03/12] update --- http/file_server.ts | 60 ++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/http/file_server.ts b/http/file_server.ts index 7a2ed599025c..135ae046df7c 100644 --- a/http/file_server.ts +++ b/http/file_server.ts @@ -518,19 +518,19 @@ function dirViewerTemplate(dirname: string, entries: EntryInfo[]): string {

Index of ${headerPaths - .map((path, index) => { - if (path === "") return ""; - const depth = headerPaths.length - index - 1; - let link; - if (depth == 0) { - link = "."; - } else { - link = "../".repeat(depth); - } - // deno-fmt-ignore - return html`${escape(path)}`; - }) - .join("/")}/ + .map((path, index) => { + if (path === "") return ""; + const depth = headerPaths.length - index - 1; + let link; + if (depth == 0) { + link = "."; + } else { + link = "../".repeat(depth); + } + // deno-fmt-ignore + return html`${escape(path)}`; + }) + .join("/")}/

@@ -541,23 +541,23 @@ function dirViewerTemplate(dirname: string, entries: EntryInfo[]): string { ${entries - .map( - (entry) => - html` - - - - - - `, - ) - .join("")} + .map( + (entry) => + html` + + + + + + `, + ) + .join("")}
- ${entry.mode} - - ${entry.size} - - ${escape(entry.name)} -
+ ${entry.mode} + + ${entry.size} + + ${escape(entry.name)} +
From 6d3fef11025a861618e6fdf2c561ae7ff06e8f3b Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 00:48:31 +0900 Subject: [PATCH 04/12] update --- net/unstable_ip.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/net/unstable_ip.ts b/net/unstable_ip.ts index 7cdcb8dd4cba..021b61a76716 100644 --- a/net/unstable_ip.ts +++ b/net/unstable_ip.ts @@ -163,7 +163,7 @@ function isValidIP(ip: string): boolean { function matchIPv4Subnet( ip: string, subnetIP: string, - prefixLength: number + prefixLength: number, ): boolean { if (prefixLength < 0 || prefixLength > 32) { return false; @@ -183,13 +183,11 @@ function matchIPv4Subnet( const mask = (0xffffffff << (32 - prefixLength)) >>> 0; - const ipInt = - (ipBytes[0]! << 24) | + const ipInt = (ipBytes[0]! << 24) | (ipBytes[1]! << 16) | (ipBytes[2]! << 8) | ipBytes[3]!; - const subnetInt = - (subnetBytes[0]! << 24) | + const subnetInt = (subnetBytes[0]! << 24) | (subnetBytes[1]! << 16) | (subnetBytes[2]! << 8) | subnetBytes[3]!; @@ -200,7 +198,7 @@ function matchIPv4Subnet( function matchIPv6Subnet( ip: string, subnetIP: string, - prefixLength: number + prefixLength: number, ): boolean { if (prefixLength < 0 || prefixLength > 128) { return false; From d5cc6198ffab72173c5d3e1ee9621baf4a428729 Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 00:54:20 +0900 Subject: [PATCH 05/12] update --- net/unstable_ip.ts | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/net/unstable_ip.ts b/net/unstable_ip.ts index 021b61a76716..dd9bbec97d88 100644 --- a/net/unstable_ip.ts +++ b/net/unstable_ip.ts @@ -23,10 +23,13 @@ export function isIPv4(addr: string): boolean { const octets = addr.split("."); - return octets.length === 4 && octets.every((octet) => { - const n = Number(octet); - return n >= 0 && n <= 255 && !isNaN(n); - }); + return ( + octets.length === 4 && + octets.every((octet) => { + const n = Number(octet); + return n >= 0 && n <= 255 && !isNaN(n); + }) + ); } /** @@ -75,10 +78,13 @@ export function isIPv6(addr: string): boolean { hextets.splice(idx, 0, ""); } - return hextets.length === 8 && hextets.every((hextet) => { - const n = hextet === "" ? 0 : parseInt(hextet, 16); - return n >= 0 && n <= 65535 && !isNaN(n); - }); + return ( + hextets.length === 8 && + hextets.every((hextet) => { + const n = hextet === "" ? 0 : parseInt(hextet, 16); + return n >= 0 && n <= 65535 && !isNaN(n); + }) + ); } /** @@ -93,13 +99,13 @@ export function isIPv6(addr: string): boolean { * * ```ts * import { matchSubnets } from "@std/net/unstable-ip" - * import { assert } from "@std/assert" + * import { assert, assertFalse } from "@std/assert" * - * assertEquals(matchSubnets("192.168.1.10", ["192.168.1.0/24"]), true); - * assertEquals(matchSubnets("192.168.2.10", ["192.168.1.0/24"]), false); + * assert(matchSubnets("192.168.1.10", ["192.168.1.0/24"])); + * assertFalse(matchSubnets("192.168.2.10", ["192.168.1.0/24"])); * - * assertEquals(matchSubnets("2001:db8::ffff", ["2001:db8::/64"]), true); - * assertEquals(matchSubnets("2001:db9::1", ["2001:db8::/64"]), false); + * assert(matchSubnets("2001:db8::ffff", ["2001:db8::/64"])); + * assertFalse(matchSubnets("2001:db9::1", ["2001:db8::/64"])); * ``` */ export function matchSubnets(addr: string, subnetOrIps: string[]): boolean { @@ -163,7 +169,7 @@ function isValidIP(ip: string): boolean { function matchIPv4Subnet( ip: string, subnetIP: string, - prefixLength: number, + prefixLength: number ): boolean { if (prefixLength < 0 || prefixLength > 32) { return false; @@ -183,11 +189,13 @@ function matchIPv4Subnet( const mask = (0xffffffff << (32 - prefixLength)) >>> 0; - const ipInt = (ipBytes[0]! << 24) | + const ipInt = + (ipBytes[0]! << 24) | (ipBytes[1]! << 16) | (ipBytes[2]! << 8) | ipBytes[3]!; - const subnetInt = (subnetBytes[0]! << 24) | + const subnetInt = + (subnetBytes[0]! << 24) | (subnetBytes[1]! << 16) | (subnetBytes[2]! << 8) | subnetBytes[3]!; @@ -198,7 +206,7 @@ function matchIPv4Subnet( function matchIPv6Subnet( ip: string, subnetIP: string, - prefixLength: number, + prefixLength: number ): boolean { if (prefixLength < 0 || prefixLength > 128) { return false; From 0d15cd51a9335cb8a85adb733282ba46304a87d5 Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 00:56:28 +0900 Subject: [PATCH 06/12] update --- net/unstable_ip.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/net/unstable_ip.ts b/net/unstable_ip.ts index dd9bbec97d88..f99cb460c3ad 100644 --- a/net/unstable_ip.ts +++ b/net/unstable_ip.ts @@ -169,7 +169,7 @@ function isValidIP(ip: string): boolean { function matchIPv4Subnet( ip: string, subnetIP: string, - prefixLength: number + prefixLength: number, ): boolean { if (prefixLength < 0 || prefixLength > 32) { return false; @@ -189,13 +189,11 @@ function matchIPv4Subnet( const mask = (0xffffffff << (32 - prefixLength)) >>> 0; - const ipInt = - (ipBytes[0]! << 24) | + const ipInt = (ipBytes[0]! << 24) | (ipBytes[1]! << 16) | (ipBytes[2]! << 8) | ipBytes[3]!; - const subnetInt = - (subnetBytes[0]! << 24) | + const subnetInt = (subnetBytes[0]! << 24) | (subnetBytes[1]! << 16) | (subnetBytes[2]! << 8) | subnetBytes[3]!; @@ -206,7 +204,7 @@ function matchIPv4Subnet( function matchIPv6Subnet( ip: string, subnetIP: string, - prefixLength: number + prefixLength: number, ): boolean { if (prefixLength < 0 || prefixLength > 128) { return false; From 8a78022ef729a6996016a6b8b56c6429c1fdaf9d Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 00:58:30 +0900 Subject: [PATCH 07/12] update --- net/unstable_ip.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/net/unstable_ip.ts b/net/unstable_ip.ts index f99cb460c3ad..3aac057f5662 100644 --- a/net/unstable_ip.ts +++ b/net/unstable_ip.ts @@ -92,7 +92,7 @@ export function isIPv6(addr: string): boolean { * * @experimental **UNSTABLE**: New API, yet to be vetted. * - * @param ip The IP address to check (IPv4 or IPv6) + * @param addr The IP address to check (IPv4 or IPv6) * @param subnetOrIps The subnet in CIDR notation (e.g., "192.168.1.0/24") or a specific IP address * @returns true if the IP address matches the subnet or IP, false otherwise * @example Check if the address is a IPv6 @@ -169,7 +169,7 @@ function isValidIP(ip: string): boolean { function matchIPv4Subnet( ip: string, subnetIP: string, - prefixLength: number, + prefixLength: number ): boolean { if (prefixLength < 0 || prefixLength > 32) { return false; @@ -189,11 +189,13 @@ function matchIPv4Subnet( const mask = (0xffffffff << (32 - prefixLength)) >>> 0; - const ipInt = (ipBytes[0]! << 24) | + const ipInt = + (ipBytes[0]! << 24) | (ipBytes[1]! << 16) | (ipBytes[2]! << 8) | ipBytes[3]!; - const subnetInt = (subnetBytes[0]! << 24) | + const subnetInt = + (subnetBytes[0]! << 24) | (subnetBytes[1]! << 16) | (subnetBytes[2]! << 8) | subnetBytes[3]!; @@ -204,7 +206,7 @@ function matchIPv4Subnet( function matchIPv6Subnet( ip: string, subnetIP: string, - prefixLength: number, + prefixLength: number ): boolean { if (prefixLength < 0 || prefixLength > 128) { return false; From 89ac606ff6c7088955f882037813a1484ee4f953 Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 01:01:55 +0900 Subject: [PATCH 08/12] update --- net/unstable_ip.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/net/unstable_ip.ts b/net/unstable_ip.ts index 3aac057f5662..269c6177ecf9 100644 --- a/net/unstable_ip.ts +++ b/net/unstable_ip.ts @@ -169,7 +169,7 @@ function isValidIP(ip: string): boolean { function matchIPv4Subnet( ip: string, subnetIP: string, - prefixLength: number + prefixLength: number, ): boolean { if (prefixLength < 0 || prefixLength > 32) { return false; @@ -189,13 +189,11 @@ function matchIPv4Subnet( const mask = (0xffffffff << (32 - prefixLength)) >>> 0; - const ipInt = - (ipBytes[0]! << 24) | + const ipInt = (ipBytes[0]! << 24) | (ipBytes[1]! << 16) | (ipBytes[2]! << 8) | ipBytes[3]!; - const subnetInt = - (subnetBytes[0]! << 24) | + const subnetInt = (subnetBytes[0]! << 24) | (subnetBytes[1]! << 16) | (subnetBytes[2]! << 8) | subnetBytes[3]!; @@ -206,7 +204,7 @@ function matchIPv4Subnet( function matchIPv6Subnet( ip: string, subnetIP: string, - prefixLength: number + prefixLength: number, ): boolean { if (prefixLength < 0 || prefixLength > 128) { return false; From 45cee381108df01c7fb965231f4d996134a66223 Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 01:27:53 +0900 Subject: [PATCH 09/12] update --- net/unstable_ip.ts | 14 ++++++-------- net/unstable_ip_test.ts | 1 + 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/net/unstable_ip.ts b/net/unstable_ip.ts index 269c6177ecf9..30d478fa161a 100644 --- a/net/unstable_ip.ts +++ b/net/unstable_ip.ts @@ -169,7 +169,7 @@ function isValidIP(ip: string): boolean { function matchIPv4Subnet( ip: string, subnetIP: string, - prefixLength: number, + prefixLength: number ): boolean { if (prefixLength < 0 || prefixLength > 32) { return false; @@ -189,11 +189,13 @@ function matchIPv4Subnet( const mask = (0xffffffff << (32 - prefixLength)) >>> 0; - const ipInt = (ipBytes[0]! << 24) | + const ipInt = + (ipBytes[0]! << 24) | (ipBytes[1]! << 16) | (ipBytes[2]! << 8) | ipBytes[3]!; - const subnetInt = (subnetBytes[0]! << 24) | + const subnetInt = + (subnetBytes[0]! << 24) | (subnetBytes[1]! << 16) | (subnetBytes[2]! << 8) | subnetBytes[3]!; @@ -204,7 +206,7 @@ function matchIPv4Subnet( function matchIPv6Subnet( ip: string, subnetIP: string, - prefixLength: number, + prefixLength: number ): boolean { if (prefixLength < 0 || prefixLength > 128) { return false; @@ -247,10 +249,6 @@ function matchIPv6Subnet( } function expandIPv6(ip: string): string | null { - if (!isIPv6(ip)) { - return null; - } - if (ip.includes(".")) { const parts = ip.split(":"); const ipv4Part = parts.pop(); diff --git a/net/unstable_ip_test.ts b/net/unstable_ip_test.ts index 890bbc894e85..90dba510e79b 100644 --- a/net/unstable_ip_test.ts +++ b/net/unstable_ip_test.ts @@ -117,6 +117,7 @@ Deno.test("matchSubnets - invalid inputs", () => { assertEquals(matchSubnets("invalid-ip", ["192.168.1.0/24"]), false); assertEquals(matchSubnets("192.168.1.10", ["invalid-subnet"]), false); assertEquals(matchSubnets("192.168.1.10", ["192.168.1.0/33"]), false); + assertEquals(matchSubnets("192.168.1.10", ["192.168.1.0/AA"]), false); assertEquals(matchSubnets("2001:db8::1", ["2001:db8::/129"]), false); }); From 3dcfa878050839c5c1832854db2e8ca0f2f8e02b Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 01:32:53 +0900 Subject: [PATCH 10/12] update --- net/unstable_ip.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/net/unstable_ip.ts b/net/unstable_ip.ts index 30d478fa161a..adbc198058cb 100644 --- a/net/unstable_ip.ts +++ b/net/unstable_ip.ts @@ -169,7 +169,7 @@ function isValidIP(ip: string): boolean { function matchIPv4Subnet( ip: string, subnetIP: string, - prefixLength: number + prefixLength: number, ): boolean { if (prefixLength < 0 || prefixLength > 32) { return false; @@ -189,13 +189,11 @@ function matchIPv4Subnet( const mask = (0xffffffff << (32 - prefixLength)) >>> 0; - const ipInt = - (ipBytes[0]! << 24) | + const ipInt = (ipBytes[0]! << 24) | (ipBytes[1]! << 16) | (ipBytes[2]! << 8) | ipBytes[3]!; - const subnetInt = - (subnetBytes[0]! << 24) | + const subnetInt = (subnetBytes[0]! << 24) | (subnetBytes[1]! << 16) | (subnetBytes[2]! << 8) | subnetBytes[3]!; @@ -206,7 +204,7 @@ function matchIPv4Subnet( function matchIPv6Subnet( ip: string, subnetIP: string, - prefixLength: number + prefixLength: number, ): boolean { if (prefixLength < 0 || prefixLength > 128) { return false; From eefa036339aa37a113e008594c6abf64c40cb0a9 Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Fri, 8 Aug 2025 01:43:42 +0900 Subject: [PATCH 11/12] update update update update update update update update update update update update update coverage update coverage update coverage update coverage --- net/unstable_ip.ts | 141 ++++++++++++++++++++++---------- net/unstable_ip_test.ts | 176 ++++++++++++++++++++++------------------ 2 files changed, 194 insertions(+), 123 deletions(-) diff --git a/net/unstable_ip.ts b/net/unstable_ip.ts index adbc198058cb..d19ccf62f864 100644 --- a/net/unstable_ip.ts +++ b/net/unstable_ip.ts @@ -130,18 +130,12 @@ function matchSubnet(addr: string, subnet: string): boolean { // Parse subnet into IP address and prefix length const [subnetIP, prefixLengthStr] = subnet.split("/"); - if (subnetIP === undefined || prefixLengthStr === undefined) { - return false; - } - - // Validate that the subnet IP is a valid IPv4 or IPv6 address - if (!isValidIP(subnetIP)) { - return false; - } - - // Parse and validate the prefix length - const prefix = parseInt(prefixLengthStr, 10); - if (isNaN(prefix)) { + if ( + !subnetIP || + subnetIP === "" || + !prefixLengthStr || + prefixLengthStr === "" + ) { return false; } @@ -156,9 +150,9 @@ function matchSubnet(addr: string, subnet: string): boolean { // Delegate to the appropriate subnet matching function if (ipIsV4) { - return matchIPv4Subnet(addr, subnetIP, prefix); + return matchIPv4Subnet(addr, subnet); } else { - return matchIPv6Subnet(addr, subnetIP, prefix); + return matchIPv6Subnet(addr, subnet); } } @@ -166,28 +160,58 @@ function isValidIP(ip: string): boolean { return isIPv4(ip) || isIPv6(ip); } -function matchIPv4Subnet( - ip: string, - subnetIP: string, - prefixLength: number, -): boolean { - if (prefixLength < 0 || prefixLength > 32) { +/** + * Checks if an IPv4 address matches a subnet or specific IPv4 address. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @param addr The IP address to check (IPv4) + * @param subnet The subnet in CIDR notation (e.g., "192.168.1.0/24") or a specific IP address + * @returns true if the IP address matches the subnet or IP, false otherwise + * @example Check if the address is a IPv6 + * + * ```ts + * import { matchIPv4Subnet } from "@std/net/unstable-ip" + * import { assert, assertFalse } from "@std/assert" + * + * assert(matchIPv4Subnet("192.168.1.10", "192.168.1.0/24")); + * assertFalse(matchIPv4Subnet("192.168.2.10", "192.168.1.0/24")); + * ``` + */ +export function matchIPv4Subnet(addr: string, subnet: string): boolean { + const [subnetIP, prefixLengthStr] = subnet.split("/"); + + const prefix = parseInt(prefixLengthStr!, 10); + if (isNaN(prefix)) { + return false; + } + + if ( + !subnetIP || + subnetIP === "" || + !prefixLengthStr || + prefixLengthStr === "" + ) { + return false; + } + + if (prefix < 0 || prefix > 32) { return false; } // Special case: /0 matches all IPv4 addresses - if (prefixLength === 0) { + if (prefix === 0) { return true; } - const ipBytes = ip.split(".").map(Number); + const ipBytes = addr.split(".").map(Number); const subnetBytes = subnetIP.split(".").map(Number); if (ipBytes.length !== 4 || subnetBytes.length !== 4) { return false; } - const mask = (0xffffffff << (32 - prefixLength)) >>> 0; + const mask = (0xffffffff << (32 - prefix)) >>> 0; const ipInt = (ipBytes[0]! << 24) | (ipBytes[1]! << 16) | @@ -201,20 +225,50 @@ function matchIPv4Subnet( return ((ipInt >>> 0) & mask) === ((subnetInt >>> 0) & mask); } -function matchIPv6Subnet( - ip: string, - subnetIP: string, - prefixLength: number, -): boolean { - if (prefixLength < 0 || prefixLength > 128) { +/** + * Checks if an IPv6 address matches a subnet or specific IPv6 address. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @param addr The IP address to check (IPv6) + * @param subnet The subnet in CIDR notation (e.g., "2001:db8::/64") or a specific IP address + * @returns true if the IP address matches the subnet or IP, false otherwise + * @example Check if the address is a IPv6 + * + * ```ts + * import { matchIPv6Subnet } from "@std/net/unstable-ip" + * import { assert, assertFalse } from "@std/assert" + * + * assert(matchIPv6Subnet("2001:db8::ffff", "2001:db8::/64")); + * assertFalse(matchIPv6Subnet("2001:db9::1", "2001:db8::/64")); + * ``` + */ +export function matchIPv6Subnet(addr: string, subnet: string): boolean { + const [subnetIP, prefixLengthStr] = subnet.split("/"); + + const prefix = parseInt(prefixLengthStr!, 10); + if (isNaN(prefix)) { + return false; + } + + if ( + !subnetIP || + subnetIP === "" || + !prefixLengthStr || + prefixLengthStr === "" + ) { return false; } - if (prefixLength === 0) { + if (prefix < 0 || prefix > 128) { + return false; + } + + if (prefix === 0) { return true; } - const ipExpanded = expandIPv6(ip); + const ipExpanded = expandIPv6(addr); const subnetExpanded = expandIPv6(subnetIP); if (!ipExpanded || !subnetExpanded) { @@ -224,8 +278,8 @@ function matchIPv6Subnet( const ipBytes = ipv6ToBytes(ipExpanded); const subnetBytes = ipv6ToBytes(subnetExpanded); - const fullBytes = Math.floor(prefixLength / 8); - const remainingBits = prefixLength % 8; + const fullBytes = Math.floor(prefix / 8); + const remainingBits = prefix % 8; for (let i = 0; i < fullBytes; i++) { if (ipBytes[i] !== subnetBytes[i]) { @@ -233,27 +287,24 @@ function matchIPv6Subnet( } } - if (remainingBits > 0 && fullBytes < 16) { + if (remainingBits > 0) { const mask = 0xff << (8 - remainingBits); - const ipByte = ipBytes[fullBytes]; - const subnetByte = subnetBytes[fullBytes]; - if (ipByte === undefined || subnetByte === undefined) { - return false; - } + const ipByte = ipBytes[fullBytes]!; + const subnetByte = subnetBytes[fullBytes]!; return (ipByte & mask) === (subnetByte & mask); } return true; } -function expandIPv6(ip: string): string | null { - if (ip.includes(".")) { - const parts = ip.split(":"); +function expandIPv6(addr: string): string | null { + if (addr.includes(".")) { + const parts = addr.split(":"); const ipv4Part = parts.pop(); if (!ipv4Part) { return null; } - const ipv4Bytes = ipv4Part.split(".").map(Number); + const ipv4Bytes = ipv4Part!.split(".").map(Number); if (ipv4Bytes.length !== 4) { return null; } @@ -261,10 +312,10 @@ function expandIPv6(ip: string): string | null { ((ipv4Bytes[0]! << 8) | ipv4Bytes[1]!).toString(16).padStart(4, "0") + ":" + ((ipv4Bytes[2]! << 8) | ipv4Bytes[3]!).toString(16).padStart(4, "0"); - ip = parts.join(":") + ":" + ipv4Hex; + addr = parts.join(":") + ":" + ipv4Hex; } - let expanded = ip; + let expanded = addr; // Handle :: if (expanded.includes("::")) { diff --git a/net/unstable_ip_test.ts b/net/unstable_ip_test.ts index 90dba510e79b..8658ddc6258d 100644 --- a/net/unstable_ip_test.ts +++ b/net/unstable_ip_test.ts @@ -1,6 +1,12 @@ // Copyright 2018-2025 the Deno authors. MIT license. -import { isIPv4, isIPv6, matchSubnets } from "./unstable_ip.ts"; +import { + isIPv4, + isIPv6, + matchIPv4Subnet, + matchIPv6Subnet, + matchSubnets, +} from "./unstable_ip.ts"; import { assertEquals } from "@std/assert"; Deno.test("isIPv4()", () => { @@ -47,93 +53,107 @@ Deno.test("isIPv6()", () => { } }); -Deno.test("matchSubnets - IPv4 exact match", () => { - assertEquals(matchSubnets("192.168.1.10", ["192.168.1.10"]), true); - assertEquals(matchSubnets("192.168.1.10", ["192.168.1.11"]), false); -}); - -Deno.test("matchSubnets - IPv4 subnet match", () => { - // Test /24 subnet - assertEquals(matchSubnets("192.168.1.10", ["192.168.1.0/24"]), true); - assertEquals(matchSubnets("192.168.1.255", ["192.168.1.0/24"]), true); - assertEquals(matchSubnets("192.168.2.10", ["192.168.1.0/24"]), false); - - // Test /16 subnet - assertEquals(matchSubnets("192.168.100.10", ["192.168.0.0/16"]), true); - assertEquals(matchSubnets("192.169.1.10", ["192.168.0.0/16"]), false); - - // Test /8 subnet - assertEquals(matchSubnets("192.100.100.100", ["192.0.0.0/8"]), true); - assertEquals(matchSubnets("193.1.1.1", ["192.0.0.0/8"]), false); -}); - -Deno.test("matchSubnets - IPv6 exact match", () => { - assertEquals(matchSubnets("2001:db8::1", ["2001:db8::1"]), true); - assertEquals(matchSubnets("2001:db8::1", ["2001:db8::2"]), false); - assertEquals(matchSubnets("::1", ["::1"]), true); -}); - -Deno.test("matchSubnets - IPv6 subnet match", () => { - // Test /64 subnet - assertEquals(matchSubnets("2001:db8::1", ["2001:db8::/64"]), true); - assertEquals(matchSubnets("2001:db8::ffff", ["2001:db8::/64"]), true); - assertEquals(matchSubnets("2001:db9::1", ["2001:db8::/64"]), false); - - // Test /32 subnet - assertEquals(matchSubnets("2001:db8:1234::1", ["2001:db8::/32"]), true); - assertEquals(matchSubnets("2001:db9::1", ["2001:db8::/32"]), false); - - // Test /128 (exact match) - assertEquals(matchSubnets("2001:db8::1", ["2001:db8::1/128"]), true); - assertEquals(matchSubnets("2001:db8::2", ["2001:db8::1/128"]), false); -}); - -Deno.test("matchSubnets - multiple subnets", () => { - const subnets = [ +Deno.test("matchSubnets()", () => { + const mixed = [ "192.168.1.0/24", "10.0.0.0/8", "2001:db8::/32", "172.16.0.100", // exact IP ]; - assertEquals(matchSubnets("192.168.1.50", subnets), true); - assertEquals(matchSubnets("10.5.5.5", subnets), true); - assertEquals(matchSubnets("2001:db8:1234::1", subnets), true); - assertEquals(matchSubnets("172.16.0.100", subnets), true); - assertEquals(matchSubnets("172.16.0.101", subnets), false); - assertEquals(matchSubnets("8.8.8.8", subnets), false); -}); - -Deno.test("matchSubnets - mixed IPv4 and IPv6", () => { - const subnets = ["192.168.1.0/24", "2001:db8::/64"]; - - assertEquals(matchSubnets("192.168.1.10", subnets), true); - assertEquals(matchSubnets("2001:db8::1", subnets), true); - assertEquals(matchSubnets("10.0.0.1", subnets), false); - assertEquals(matchSubnets("2001:db9::1", subnets), false); -}); + const list = [ + // Multiple and mixed subnets + { addr: "192.168.1.50", subnets: mixed, expected: true }, + { addr: "10.5.5.5", subnets: mixed, expected: true }, + { addr: "172.16.0.100", subnets: mixed, expected: true }, + { addr: "172.16.0.101", subnets: mixed, expected: false }, + { addr: "8.8.8.8", subnets: mixed, expected: false }, + { addr: "2001:db8:1234::1", subnets: mixed, expected: true }, + { addr: "2001:db9::1", subnets: mixed, expected: false }, + + // Invalid inputs + { addr: "invalid-ip", subnets: ["192.168.1.0/24"], expected: false }, + { addr: "192.168.1.10", subnets: ["invalid-subnet"], expected: false }, + { addr: "192.168.1.10", subnets: ["192.168.1.0/33"], expected: false }, + { addr: "192.168.1.10", subnets: ["192.168.1.0/AA"], expected: false }, + { addr: "192.168.1.10", subnets: ["192.168.1.0/"], expected: false }, + { addr: "2001:db8::1", subnets: ["2001:db8::/129"], expected: false }, + { addr: "2001:db8::1", subnets: ["2001:db8::/"], expected: false }, + { addr: "192.168.1.10", subnets: [], expected: false }, + ]; -Deno.test("matchSubnets - invalid inputs", () => { - assertEquals(matchSubnets("invalid-ip", ["192.168.1.0/24"]), false); - assertEquals(matchSubnets("192.168.1.10", ["invalid-subnet"]), false); - assertEquals(matchSubnets("192.168.1.10", ["192.168.1.0/33"]), false); - assertEquals(matchSubnets("192.168.1.10", ["192.168.1.0/AA"]), false); - assertEquals(matchSubnets("2001:db8::1", ["2001:db8::/129"]), false); + for (const { addr, subnets, expected } of list) { + assertEquals(matchSubnets(addr, subnets), expected); + } }); -Deno.test("matchSubnets - edge cases", () => { - // Empty subnets array - assertEquals(matchSubnets("192.168.1.10", []), false); +Deno.test("matchIPv4Subnet()", () => { + const list: Array<{ addr: string; subnet: string; expected: boolean }> = [ + { addr: "192.168.1.10", subnet: "192.168.1.0/24", expected: true }, + { addr: "192.168.1.11", subnet: "/32", expected: false }, + { addr: "192.168.1", subnet: "192.168.1/32", expected: false }, + { addr: "192.168.1.1", subnet: "192.168.1.0/", expected: false }, + { addr: "192.168.1.1", subnet: "192.168.1.0/33", expected: false }, + { addr: "192.168.1.1", subnet: "192.168.1.0/0", expected: true }, + ]; - // /0 subnet (all IPs) - assertEquals(matchSubnets("192.168.1.10", ["0.0.0.0/0"]), true); - assertEquals(matchSubnets("8.8.8.8", ["0.0.0.0/0"]), true); + for (const { addr, subnet, expected } of list) { + assertEquals(matchIPv4Subnet(addr, subnet), expected); + } +}); - // IPv6 /0 subnet - assertEquals(matchSubnets("2001:db8::1", ["::/0"]), true); - assertEquals(matchSubnets("fe80::1", ["::/0"]), true); +Deno.test("matchIPv6Subnet()", () => { + const list = [ + // Basic functionality + { addr: "2001:db8::1", subnet: "2001:db8::/64", expected: true }, + + // Invalid prefix lengths + { addr: "2001:db8::1", subnet: "2001:db8::/129", expected: false }, + { addr: "2001:db8::1", subnet: "/129", expected: false }, + { addr: "2001:db8::1", subnet: "2001:db8::/", expected: false }, + + // Invalid address formats + { addr: "2001:db8", subnet: "2001:db8::/64", expected: false }, + { addr: "2001:db8::1", subnet: "2001:db8", expected: false }, + + // expandIPv6 edge cases + { addr: "2001:db8::192.168.1", subnet: "2001:db8::/64", expected: false }, + { addr: "gggg::1", subnet: "2001:db8::/64", expected: false }, + { addr: "invalid", subnet: "2001:db8::/64", expected: false }, + + // Zero prefix (matches all) + { addr: "2001:db8::1", subnet: "::/0", expected: true }, + + // Remaining bits test + { addr: "2001:db8::1", subnet: "2001:db8::/121", expected: true }, + + // Additional coverage cases + { addr: "2001:db8::", subnet: "2001:db8::/64", expected: true }, + { addr: "::", subnet: "::/128", expected: true }, + + // Additional edge cases + { addr: "2001:db8::1", subnet: "2001:db8::1/-1", expected: false }, + { addr: "2001:db8::1", subnet: "2001:db8::1/abc", expected: false }, + + //IPv6 with embedded IPv4 + { + addr: "2001:db8::ffff:192.168.1.1.1", + subnet: "2001:db8::/64", + expected: false, + }, + { + addr: "2001:db8::ffff:192.168.1", + subnet: "2001:db8::/64", + expected: false, + }, + { + addr: "::ffff:192.168.1.1:", + subnet: "::ffff:0.0.0.0/128", + expected: false, + }, + ]; - // /32 for IPv4 (exact match) - assertEquals(matchSubnets("192.168.1.10", ["192.168.1.10/32"]), true); - assertEquals(matchSubnets("192.168.1.11", ["192.168.1.10/32"]), false); + for (const { addr, subnet, expected } of list) { + assertEquals(matchIPv6Subnet(addr, subnet), expected); + } }); From cd3ba91682e22ebb3902e72711ddeb79699d97c9 Mon Sep 17 00:00:00 2001 From: Octo8080 Date: Sat, 9 Aug 2025 10:00:28 +0900 Subject: [PATCH 12/12] update coverage --- net/unstable_ip_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/unstable_ip_test.ts b/net/unstable_ip_test.ts index 8658ddc6258d..58be493078ef 100644 --- a/net/unstable_ip_test.ts +++ b/net/unstable_ip_test.ts @@ -88,7 +88,7 @@ Deno.test("matchSubnets()", () => { }); Deno.test("matchIPv4Subnet()", () => { - const list: Array<{ addr: string; subnet: string; expected: boolean }> = [ + const list = [ { addr: "192.168.1.10", subnet: "192.168.1.0/24", expected: true }, { addr: "192.168.1.11", subnet: "/32", expected: false }, { addr: "192.168.1", subnet: "192.168.1/32", expected: false },