Skip to content

Instantly share code, notes, and snippets.

@kLiHz
Last active July 28, 2024 05:02
Show Gist options
  • Save kLiHz/a47667cb325a277eeb6859b28ae38cc9 to your computer and use it in GitHub Desktop.
Save kLiHz/a47667cb325a277eeb6859b28ae38cc9 to your computer and use it in GitHub Desktop.
Validate IP address (IPv4 / IPv6) in JavaScript / TypeScript
// WARNING: These code may be incorrect and may not be production ready, so use at your own risk.
export const dotDecimalIPv4Regex = /^((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)$/;
const dottedComponentRegex = /^((0(?:x|X)[a-fA-F0-9]+)|(0[0-7]*)|([1-9]\d*))$/;
export function isIPv4(s: string): boolean {
const decimals = s.split('.');
// https://stackoverflow.com/questions/10133820/is-there-any-documentation-for-omitting-zeroes-in-dot-decimal-notation-of-ipv4-a
// https://linux.die.net/man/3/inet_aton
return decimals.length <= 4
&& decimals.every(i => dottedComponentRegex.test(i))
&& decimals.map(d => d.startsWith('0')
? (d.startsWith('0X') || d.startsWith('0x'))
? Number.parseInt(d, 16)
: Number.parseInt(d, 8)
: Number.parseInt(d, 10)).every((d, i, a) => d < ((i < a.length - 1) ? 256 : (2 ** (8 * (4 - decimals.length + 1)))))
;
}
export function validIPv4(s: string): false | string {
const decimals = s.split('.');
if (
decimals.length <= 4
&& decimals.every(i => dottedComponentRegex.test(i))
) {
const n = decimals.map(
d => d.startsWith('0')
? (d.startsWith('0X') || d.startsWith('0x'))
? Number.parseInt(d, 16)
: Number.parseInt(d, 8)
: Number.parseInt(d, 10)
);
if (n.length == 4) {
return n.every(i => i < 256) ? n.join('.') : false;
}
const a = n.slice(0, -1);
const b = n.slice(-1)[0];
if (a.every(i => i < 256) && (b < 2 ** (32 - 8 * a.length))) {
return [
(a[0] ?? 0) + (255 & (b >> 24)),
(a[1] ?? 0) + (255 & (b >> 16)),
(a[2] ?? 0) + (255 & (b >> 8)),
(a[3] ?? 0) + (255 & (b)),
].join('.');
}
}
return false;
}
// untidy solution, using try-catch wrapped URL constructor
export function validateIPv4(s: string): false | string {
const t = s.split('.');
if (t.length <= 4 && t.every(i => dottedComponentRegex.test(i))) {
try {
const u = new URL(`http://${s}:8080`);
return u.hostname;
} catch (_e) {
return false;
}
}
return false;
}
// WARNING: These code may be incorrect and may not be production ready, so use at your own risk.
import { dotDecimalIPv4Regex } from "./IPv4.ts";
export function isIPv6(s: string): boolean {
const parts = s.split(':');
if (parts[0] === '') {
if (s.startsWith('::')) parts[0] = '0';
else return false;
}
if (parts[parts.length - 1] === '') {
if (s.endsWith('::')) parts[parts.length - 1] = '0';
else return false;
}
const hasTraillingIPv4 = dotDecimalIPv4Regex.test(parts.at(-1)!);
const segments = hasTraillingIPv4 ? parts.slice(0, -1) : parts;
const innerZeroSegments = segments.filter(i => i === '').length;
if (innerZeroSegments > 1) return false;
const hexSegmentsLength = (hasTraillingIPv4 ? 6 : 8);
return (innerZeroSegments == 0
? segments.length == hexSegmentsLength
: segments.length <= hexSegmentsLength)
&& segments.every(i => /^[A-Fa-f0-9]{0,4}$/.test(i))
;
}
export function isIPv6_1(s: string) {
const t = s.split('::');
if (t.length == 2) {
const u = (t[0] ? t[0].split(':') : []).concat(['0']).concat(t[1] ? t[1].split(':') : []);
const hasTraillingIPv4 = u.at(-1) && dotDecimalIPv4Regex.test(u.at(-1)!);
const n = hasTraillingIPv4 ? u.slice(0, -1) : u;
return n.length <= (hasTraillingIPv4 ? 6 : 8) && n.every(i => /^[A-Fa-f0-9]{1,4}$/.test(i));
} else if (t.length == 1) {
const u = t[0].split(':');
const hasTraillingIPv4 = u.at(-1) && dotDecimalIPv4Regex.test(u.at(-1)!);
const n = hasTraillingIPv4 ? u.slice(0, -1) : u;
return (n.length == (hasTraillingIPv4 ? 6 : 8)) && n.every(i => /^[A-Fa-f0-9]{1,4}$/.test(i));
} else {
return false;
}
}
// untidy solution, using try-catch wrapped URL constructor
export function validateIPv6(s: string): false | string {
if (/^[0-9a-fA-F\.\:]+$/.test(s)) {
try {
const u = new URL(`http://[${s}]:8080`);
return u.hostname.slice(1, -1);
} catch (_e) {
return false;
}
}
return false;
}
const validIPv4Addresses = [
'1.2.3.4',
'127.0.0.1',
'127.1',
'127.999',
'1213787299',
'0X12.0x34.0x56.0x78',
'0X12.0x34.0x5678',
'0X12.0x345678',
'0x12abcdef',
'010.010.010.010',
]
const invalidIPv4Addresses = [
'',
'1..1',
'9.1.3.abc',
'xx',
'0xffffffff1',
'1.2.3.4.5',
'256.0.0.0',
'08.08.08.08',
]
const validIPv6Addresses = [
'2001::',
'2001:db8::',
'::',
'::1',
'::456:789',
'2001:db8::1',
'2001:db8::456:789',
'2001:db8:0:0:1:2:3:4',
'2001:db8:0::1:2:3:4',
'::127.0.0.1',
'::ff:127.0.0.1',
'2001::127.0.0.1',
'2001:db8::127.0.0.1',
'2001:db8:1:2:0:0:127.0.0.1',
'2001:db8:1:2:0::127.0.0.1',
'2001:db8:1:2::127.0.0.1',
]
const invalidIPv6Addresses = [
'',
'2001:',
':2001:',
':2001',
'2001:db8:::456:789',
'2001:db8::456:789:',
'2001:db8::456::789',
'2001:db8::456::789:',
'2001:db8:0::1:2:3:4:5',
'2001:db8:1:2:0:0:0:127.0.0.1',
'2001:db8:1:2:0:0::127.0.0.1',
'1:2:3:4:1:2:3:4:',
':1:2:3:4:1:2:3:4:',
':1:2:3:4:1:2:3:4',
'1:2:3:4:1:2:3:4:5',
':1:2:3:4:1:2:3:4:5:',
'::127.0.1',
]
import { isIPv4, validIPv4, validateIPv4 } from "./IPv4.ts"
console.log('Valid IPv4 address should pass the test.');
console.log(validIPv4Addresses.map(i => [isIPv4(i), validIPv4(i), validateIPv4(i), i]));
console.log('Invalid IPv4 address should fail the test (returning false).');
console.log(invalidIPv4Addresses.map(i => [isIPv4(i), validIPv4(i), validateIPv4(i), i]));
import { isIPv6, isIPv6_1, validateIPv6 } from "./IPv6.ts"
console.log('Valid IPv6 address should pass the test.');
console.log(validIPv6Addresses.map(i => [isIPv6(i), isIPv6_1(i), validateIPv6(i), i]));
console.log('Invalid IPv6 address should fail the test (returning false).');
console.log(invalidIPv6Addresses.map(i => [isIPv6(i), isIPv6_1(i), validateIPv6(i), i]));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment