Skip to content

feat(net/unstable): added ip functionalities #6765

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

WasixXD
Copy link
Contributor

@WasixXD WasixXD commented Jul 16, 2025

Hopefully closes #6722

This PR adds the isIPv4(addr: string): boolean and isIPv6(addr: string): boolean functions

@WasixXD WasixXD requested a review from kt3k as a code owner July 16, 2025 18:46
@github-actions github-actions bot added the net label Jul 16, 2025
@github-actions github-actions bot added the http label Jul 16, 2025
Copy link

codecov bot commented Jul 16, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 94.15%. Comparing base (fc59ca6) to head (7a9fed5).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6765      +/-   ##
==========================================
+ Coverage   94.14%   94.15%   +0.01%     
==========================================
  Files         586      588       +2     
  Lines       42465    42497      +32     
  Branches     6701     6716      +15     
==========================================
+ Hits        39980    40015      +35     
+ Misses       2435     2433       -2     
+ Partials       50       49       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@iuioiua iuioiua left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick pass

return html`<a href="${link}">${escape(path)}</a>`;
})
.join("/")}/
Index of ${
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems these formatting changes were made because your Deno version is out-of-date. Please update Deno and run deno fmt to revert these changes.

net/deno.json Outdated
@@ -4,6 +4,7 @@
"exports": {
".": "./mod.ts",
"./get-available-port": "./get_available_port.ts",
"./ip-utils": "./ip_utils.ts",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"./ip-utils": "./ip_utils.ts",
"./ip": "./ip.ts",

This is sufficient

net/ip_utils.ts Outdated
@@ -0,0 +1,137 @@
// Copyright 2018-2025 the Deno authors. MIT license.

const IPV4_REGEX = /^[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}$/;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you get the regex? Genuine question. Do you have a reference that you can link to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it from https://github.com/denoland/fresh/pull/3035/files i thought it was clever

net/ip_utils.ts Outdated
Comment on lines 20 to 29
export const distinctRemoteAddr = (
remoteAddr: string,
): AddressType | undefined => {
if (isIPv4(remoteAddr)) {
return "IPv4";
}
if (isIPv6(remoteAddr)) {
return "IPv6";
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO I have a feeling this function simply isn't needed and adding it now might be premature. If it were up to me, I'd wait till there's sufficient demand for such a function before adding it to the API.

net/ip_utils.ts Outdated
Comment on lines 34 to 35
* @param addr - IPv4 address in a string format (e.g., "192.168.0.1").
* @returns a boolean indicating if the string is a IPv4 address
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param addr - IPv4 address in a string format (e.g., "192.168.0.1").
* @returns a boolean indicating if the string is a IPv4 address
* @param addr IPv4 address in a string format (e.g., "192.168.0.1").
* @returns A boolean indicating if the string is a IPv4 address

Nits

net/ip_utils.ts Outdated
* @returns a boolean indicating if the string is a valid IPv4 address
*/
export function isValidIPv4(addr: string): boolean {
if (addr === "localhost") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localhost isn't an IP address.

net/ip_utils.ts Outdated
* @param addr - IPv4 address in a string format (e.g., "192.168.0.1").
* @returns a boolean indicating if the string is a valid IPv4 address
*/
export function isValidIPv4(addr: string): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having both isValidIPv4() and isIPv4() is confusing. We should likely just have one, and I think it should be named isIPv4(). Ditto for the IPv6 function.

net/ip_utils.ts Outdated
* @returns a boolean indicating if the string is a IPv6 address
*/
export function isIPv6(addr: string): boolean {
return addr.includes(":");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't correct

net/ip_utils.ts Outdated
Comment on lines 121 to 137
export function matchSubnet(subnet: string, address: string): boolean {
// 192.128.0.1 === 192.128.0.1
if (subnet === address) return true;

if (subnet.includes("/")) {
const [range, bitStr] = subnet.split("/");
if (range && bitStr) {
const bits = parseInt(bitStr, 0);
const zeroes = 32 - bits;
const mask = bits === 0 ? 0 : (~0 << zeroes) >>> 0;

return (ipToInt(address) & mask) === (ipToInt(range) & mask);
}
}

return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps, this can be implemented in a follow up PR, as to get this one landed sooner.

Comment on lines 3 to 5
import { assert } from "../assert/assert.ts";
import { assertEquals } from "../assert/equals.ts";
import { assertFalse } from "../assert/false.ts";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { assert } from "../assert/assert.ts";
import { assertEquals } from "../assert/equals.ts";
import { assertFalse } from "../assert/false.ts";
import { assert, assertEquals, assertFalse } from "@std/assert";

If you want.

@github-actions github-actions bot removed the http label Jul 17, 2025
@WasixXD WasixXD changed the title feat(net): add ip_utils feat(net): added ip functionalities Jul 17, 2025
@WasixXD WasixXD requested a review from iuioiua July 17, 2025 16:39
Copy link
Contributor

@0f-0b 0f-0b left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The syntax of valid IP addresses is described in the URL standard. Please follow that.

Also, I think it would be a waste to parse only to throw the result away, so I suggest adding functions that return the parsed address. They can internally share an implementation like this:

function ipv4Parser(str: string, buf: Uint8Array): boolean {
  // the actual implementation…
}

function ipv6Parser(str: string, buf: Uint8Array): boolean {
  // maybe reuse `ipv4Parser` for IPv6 addresses in the third form…
}

const buf = new Uint8Array(16);

export function isIPv4(str: string): boolean {
  return ipv4Parser(str, buf);
}

export function isIPv6(str: string): boolean {
  return ipv6Parser(str, buf);
}

export function parseIPv4(str: string): Uint8Array | null {
  return ipv4Parser(str, buf) ? buf.slice(0, 4) : null;
}

export function parseIPv6(str: string): Uint8Array | null {
  return ipv6Parser(str, buf) ? buf.slice(0, 16) : null;
}

export function parseIPv4Into(str: string, buf: Uint8Array): boolean {
  if (buf.length !== 4) {
    throw new TypeError("IPv4 output buffer must have a length of 4");
  }
  return ipv4Parser(str, buf);
}

export function parseIPv6Into(str: string, buf: Uint8Array): boolean {
  if (buf.length !== 16) {
    throw new TypeError("IPv6 output buffer must have a length of 16");
  }
  return ipv6Parser(str, buf);
}

net/ip.ts Outdated
// Copyright 2018-2025 the Deno authors. MIT license.

/**
* Validates whether a given string is a valid IPv4 address
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Validates whether a given string is a valid IPv4 address
* Validates whether a given string is a valid IPv4 address.

Nit: punctuation

net/ip.ts Outdated
* @returns A boolean indicating if the string is a valid IPv4 address
*
* @example Check if the address is a IPv4
* ```ts no-assert ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* ```ts no-assert ignore
* ```ts

I don't see a reason why this should be ignored. Ditto for the other snippets.

net/ip.ts Outdated
*
* assert(isIPv4(correctIp))
* assertFalse(isIPv4(incorrectIp))
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*

Nit

net/ip.ts Outdated
Comment on lines 23 to 36
const parts = addr.split(".");

if (parts.length <= 0 || parts.length < 4) return false;

for (const part of parts) {
const n = parseInt(part);

// n >= 256 or is negative
if (n >= Math.pow(2, 8) || n < 0) {
return false;
}
}

return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const parts = addr.split(".");
if (parts.length <= 0 || parts.length < 4) return false;
for (const part of parts) {
const n = parseInt(part);
// n >= 256 or is negative
if (n >= Math.pow(2, 8) || n < 0) {
return false;
}
}
return true;
const octets = addr.split(".");
return octets.length === 4 && octets.every((octet) => {
const n = parseInt(octet);
return n >= 0 && n <= 255;
});

This is how I would've written it. What you had before allowed strings with 4 or more octets, which is invalid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very clever, i didn´t know about this every function

net/mod.ts Outdated
@@ -17,3 +17,4 @@
*/

export * from "./get_available_port.ts";
export * from "./ip.ts";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export * from "./ip.ts";

Unstable (new) modules aren't usually included in the mod.ts file.

net/deno.json Outdated
@@ -4,6 +4,7 @@
"exports": {
".": "./mod.ts",
"./get-available-port": "./get_available_port.ts",
"./ip": "./ip.ts",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"./ip": "./ip.ts",
"./unstable-ip": "./unstable_ip.ts",

I forgot that new modules need to have the "unstable" prefix.

net/ip.ts Outdated
Comment on lines 59 to 83
// more than one use of ::
if ([...addr.matchAll(/::/g)].length > 1) {
return false;
}

const parts = addr.split(":");

// insert "" to the sequence when encountering a :: to normalize the hextets
for (let i = 0; i < parts.length; i++) {
if (parts[i] == "" && parts.length < 8) {
parts.splice(i, 0, "");
}
}

if (parts.length <= 0 || parts.length < 8) return false;

for (const part of parts) {
const n = part == "" ? 0 : parseInt(part, 16);

if (n >= Math.pow(2, 16) || n < 0 || isNaN(n)) {
return false;
}
}

return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// more than one use of ::
if ([...addr.matchAll(/::/g)].length > 1) {
return false;
}
const parts = addr.split(":");
// insert "" to the sequence when encountering a :: to normalize the hextets
for (let i = 0; i < parts.length; i++) {
if (parts[i] == "" && parts.length < 8) {
parts.splice(i, 0, "");
}
}
if (parts.length <= 0 || parts.length < 8) return false;
for (const part of parts) {
const n = part == "" ? 0 : parseInt(part, 16);
if (n >= Math.pow(2, 16) || n < 0 || isNaN(n)) {
return false;
}
}
return true;
// more than one use of ::
if (addr.split("::").length > 2) return false;
const hextets = addr.split(":");
// insert "" to the sequence when encountering a :: to normalize the hextets
while (hextets.length < 8) {
const idx = hextets.indexOf("");
if (idx === -1) break;
hextets.splice(idx, 0, "");
}
return hextets.length === 8 && hextets.every((hextet) => {
const n = hextet == "" ? 0 : parseInt(hextet, 16);
return n >= 0 && n <= 65535;
});

net/ip_test.ts Outdated
import { isIPv4, isIPv6 } from "./ip.ts";
import { assertEquals } from "@std/assert";

Deno.test("isIPv4() returns boolean for list of IPv4s", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Deno.test("isIPv4() returns boolean for list of IPv4s", () => {
Deno.test("isIPv4()", () => {

This should suffice. Ditto for the other test.

@iuioiua
Copy link
Contributor

iuioiua commented Jul 18, 2025

parse only to throw the result away

IMO, the current approach is fine for now.

net/ip.ts Outdated
if (parts.length <= 0 || parts.length < 4) return false;

for (const part of parts) {
const n = parseInt(part);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because parseInt returns 1 for "1a" for example, isIPv4("1a.1.1.1") is currently true

net/ip.ts Outdated
const n = parseInt(part);

// n >= 256 or is negative
if (n >= Math.pow(2, 8) || n < 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to check if n is NaN. Currently isIPv4("a.b.c.d") seems returning true

@kt3k kt3k changed the title feat(net): added ip functionalities feat(net/unstable): added ip functionalities Jul 18, 2025
@kt3k
Copy link
Member

kt3k commented Jul 18, 2025

IPv6 has the form of x:x:x:x:x:x:d.d.d.d where 0 <= x <= ffff and 0 <= d <= 255.

See 3. of section 2.2 of RFC4291 for example https://www.rfc-editor.org/rfc/rfc4291#section-2.2

net/ip.ts Outdated
*
* ```
*/
export function isIPv4(addr: string): boolean {
Copy link
Member

@kt3k kt3k Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this name conforms with our naming convention. We usually avoid capitalized acronyms (like IP). ref https://docs.deno.com/runtime/contributing/style_guide/#naming-convention

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this too initially but ended up just thinking this looks fine.

@iuioiua
Copy link
Contributor

iuioiua commented Jul 21, 2025

IPv6 has the form of x:x:x:x:x:x:d.d.d.d where 0 <= x <= ffff and 0 <= d <= 255.

See 3. of section 2.2 of RFC4291 for example https://www.rfc-editor.org/rfc/rfc4291#section-2.2

That section states that this is an additional alternative form for IPv6 addresses. I used to be a network engineer and have never used that form or seen it used. I suggest not implementing for that form until there's sufficient demand for it, which, I predict there never will be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add IP utils on @std/net
4 participants