diff --git a/.size-limit.json b/.size-limit.json index 02f2520..6bd29a0 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -9,7 +9,7 @@ "LICENSE", "package-main.json" ], - "limit": "60.40 kB", + "limit": "61.052 kB", "brotli": false, "gzip": false }, @@ -18,7 +18,7 @@ "path": [ "target/*/core.*" ], - "limit": "41.25 kB", + "limit": "41.55 kB", "brotli": false, "gzip": false }, @@ -32,7 +32,7 @@ "LICENSE", "package-main.json" ], - "limit": "18.30 kB", + "limit": "18.55 kB", "gzip": true }, { @@ -40,7 +40,7 @@ "path": [ "target/cjs" ], - "limit": "25.40 kB", + "limit": "25.55 kB", "brotli": false, "gzip": false }, @@ -49,14 +49,14 @@ "path": [ "target/esm" ], - "limit": "21.00 kB", + "limit": "21.15 kB", "brotli": false, "gzip": false }, { "name": "libdefs", "path": "target/dts", - "limit": "7.15 kB", + "limit": "7.16 kB", "brotli": false, "gzip": false } diff --git a/README.md b/README.md index 7f2c25f..00f2b0a 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,19 @@ Temporary workaround to avoid refactoring is using `overrides` / `resolutions` i Browser-compatible core build is available as `@webpod/ip/core`: it omits `node:os` dependency and polyfills the `Buffer` API. +### Strict mode +By default, the library is in the `strict` mode that rejects non-canonical embedded IPv4 in IPv6. Switch to legacy flow if needed: + +```ts +import { Address } from '@webpod/ip' + +Address.from('::ffff:5.6.7.8') // ok +Address.from('1:2:3:4::5.6.7.8') // throws + +Address.strict = false +Address.from('1:2:3:4::5.6.7.8') // now it's fine +``` + ## Usage The API is fully compatible with the latest `ip@2.0.1` but enforces stricter validations. See [coherence.md](./COHERENCE.md) for details. diff --git a/src/main/ts/core.ts b/src/main/ts/core.ts index d1e32c4..41c9f38 100644 --- a/src/main/ts/core.ts +++ b/src/main/ts/core.ts @@ -157,6 +157,8 @@ export class Address { return o } + static strict = true + static from(raw: Raw): Address { if (raw instanceof Address) return this.create(raw.big, raw.family, raw.raw) if (typeof raw === 'string') return this.fromString(raw.toLowerCase()) @@ -334,18 +336,18 @@ export class Address { if (addr === '0') return this.create(0n, 4, addr) return addr.includes(':') - ? this.fromIPv6(addr) + ? this.fromIPv6(addr, this.strict) : this.fromIPv4(addr) } - private static fromIPv6(addr: string): Address { + private static fromIPv6(addr: string, strict?: boolean): Address { const al = addr.length const sep = addr.indexOf('::') if ( al > IPV6_LEN_LIM || sep !== -1 && addr.indexOf('::', sep + 1) !== -1 // only one '::' allowed ) - throw new Error(`Invalid address: ${addr}`) + throw new Error(`Invalid address0: ${addr}`) const groups: number[] = [] let p = 0, gc = -1 @@ -360,15 +362,16 @@ export class Address { if (sep === -1 || (end !== sep && end !== sep + 1 + +last)) throw new Error(`Invalid address: ${addr}`) gc = groups.length - } else if (last && v.includes('.')) { - // embedded IPv4 - if ( - groups.length > 6 || + } else if (last && v.includes('.')) { // embedded IPv4 + if (gc === -1 ? groups.length !== 6 : groups.length > 5) + throw new Error(`Invalid address: ${addr}`) + + if (strict && ( gc === groups.length || - (gc === -1 && groups.length !== 6) || groups[groups.length - 1] !== 0xffff || groups.slice(0, -1).some(x => x !== 0) - ) throw new Error(`Invalid address: ${addr}`) + )) + throw new Error(`Invalid address: ${addr}`) const long = Address.normalizeToLong(v, true) if (long === -1) throw new Error(`Invalid address: ${addr}`) @@ -382,7 +385,7 @@ export class Address { p = i + 1 } const offset = 8 - groups.length - if (gc === -1 ? offset !== 0 : offset < 1) throw new Error(`Invalid address: ${addr}`) + if (gc === -1 ? offset !== 0 : offset < 1) throw new Error(`Invalid address4: ${addr}`) let big = 0n for (let i = 0; i < 8; i++) { diff --git a/src/test/js/export.test.js b/src/test/js/export.test.js index a3ec4ad..5ae65ac 100644 --- a/src/test/js/export.test.js +++ b/src/test/js/export.test.js @@ -9,6 +9,7 @@ describe('core', () => { assert.equal(typeof core.Address, 'function', 'core.Address') assert.equal(typeof core.Address.fromPrefixLen, 'function', 'core.Address.fromPrefixLen') assert.equal(typeof core.Address.parseCidr, 'function', 'core.Address.parseCidr') + assert.equal(typeof core.Address.strict, 'boolean', 'core.Address.strict') assert.equal(typeof core.cidr, 'function', 'core.cidr') assert.equal(typeof core.cidrSubnet, 'function', 'core.cidrSubnet') assert.equal(typeof core.fromLong, 'function', 'core.fromLong') @@ -40,6 +41,7 @@ describe('index', () => { assert.equal(typeof index.Address, 'function', 'index.Address') assert.equal(typeof index.Address.fromPrefixLen, 'function', 'index.Address.fromPrefixLen') assert.equal(typeof index.Address.parseCidr, 'function', 'index.Address.parseCidr') + assert.equal(typeof index.Address.strict, 'boolean', 'index.Address.strict') assert.equal(typeof index.address, 'function', 'index.address') assert.equal(typeof index.addresses, 'function', 'index.addresses') assert.equal(typeof index.cidr, 'function', 'index.cidr') diff --git a/src/test/ts/ipv6.test.ts b/src/test/ts/ipv6.test.ts new file mode 100644 index 0000000..9b038b0 --- /dev/null +++ b/src/test/ts/ipv6.test.ts @@ -0,0 +1,610 @@ +/** + * This test cases was borrowed and slightly adapted from richb-intermapper/IPv6-Regex. + * All credit goes to Rich Brown, Dartware, intermapper.com. + * + * Source references: + * + * https://github.com/richb-intermapper/IPv6-Regex/blob/master/test-ipv6-regex.pl + * + * Original license: + * + * IPv6 Regex by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License To attribute this work, place a link to http://intermapper.com in the Credits or About... window of your software. We would love to have you send any changes to the code or regular expression back to us at support@intermapper.com. + */ + +import { test, describe, beforeAll, afterAll, expect } from 'vitest' +import { isV6Format, Address } from '../../main/ts/core.ts' + +describe('ipv6', () => { + beforeAll(() => Address.strict = false) + afterAll(() => Address.strict = true) + + function ipv6test(is: 1 | false, addr: string) { + const expected = !!is + test(`${addr} should ${expected ? '' : 'not '}be valid`, () => { + expect(isV6Format(addr)).toBe(expected) + }) + } + +// The test cases... + ipv6test(!1,"")// empty string + ipv6test(1,"::1")// loopback, compressed, non-routable + ipv6test(1,"::")// unspecified, compressed, non-routable + ipv6test(1,"0:0:0:0:0:0:0:1")// loopback, full + ipv6test(1,"0:0:0:0:0:0:0:0")// unspecified, full + ipv6test(1,"2001:DB8:0:0:8:800:200C:417A")// unicast, full + ipv6test(1,"FF01:0:0:0:0:0:0:101")// multicast, full + ipv6test(1,"2001:DB8::8:800:200C:417A")// unicast, compressed + ipv6test(1,"FF01::101")// multicast, compressed + ipv6test(!1,"2001:DB8:0:0:8:800:200C:417A:221")// unicast, full + ipv6test(!1,"FF01::101::2")// multicast, compressed + ipv6test(1,"fe80::217:f2ff:fe07:ed62") + + ipv6test(1,"2001:0000:1234:0000:0000:C1C0:ABCD:0876") + ipv6test(1,"3ffe:0b00:0000:0000:0001:0000:0000:000a") + ipv6test(1,"FF02:0000:0000:0000:0000:0000:0000:0001") + ipv6test(1,"0000:0000:0000:0000:0000:0000:0000:0001") + ipv6test(1,"0000:0000:0000:0000:0000:0000:0000:0000") + ipv6test(!1,"02001:0000:1234:0000:0000:C1C0:ABCD:0876") // extra 0 not allowed! + ipv6test(!1,"2001:0000:1234:0000:00001:C1C0:ABCD:0876") // extra 0 not allowed! +// ipv6test(1," 2001:0000:1234:0000:0000:C1C0:ABCD:0876") // leading space +// ipv6test(1,"2001:0000:1234:0000:0000:C1C0:ABCD:0876") // trailing space +// ipv6test(1," 2001:0000:1234:0000:0000:C1C0:ABCD:0876 ") // leading and trailing space + ipv6test(!1,"2001:0000:1234:0000:0000:C1C0:ABCD:0876 0") // junk after valid address + ipv6test(!1,"2001:0000:1234: 0000:0000:C1C0:ABCD:0876") // internal space + + ipv6test(!1,"3ffe:0b00:0000:0001:0000:0000:000a") // seven segments + ipv6test(!1,"FF02:0000:0000:0000:0000:0000:0000:0000:0001") // nine segments + ipv6test(!1,"3ffe:b00::1::a") // double "::" + ipv6test(!1,"::1111:2222:3333:4444:5555:6666::") // double "::" + ipv6test(1,"2::10") + ipv6test(1,"ff02::1") + ipv6test(1,"fe80::") + ipv6test(1,"2002::") + ipv6test(1,"2001:db8::") + ipv6test(1,"2001:0db8:1234::") + ipv6test(1,"::ffff:0:0") + ipv6test(1,"::1") + ipv6test(1,"1:2:3:4:5:6:7:8") + ipv6test(1,"1:2:3:4:5:6::8") + ipv6test(1,"1:2:3:4:5::8") + ipv6test(1,"1:2:3:4::8") + ipv6test(1,"1:2:3::8") + ipv6test(1,"1:2::8") + ipv6test(1,"1::8") + ipv6test(1,"1::2:3:4:5:6:7") + ipv6test(1,"1::2:3:4:5:6") + ipv6test(1,"1::2:3:4:5") + ipv6test(1,"1::2:3:4") + ipv6test(1,"1::2:3") + ipv6test(1,"1::8") + ipv6test(1,"::2:3:4:5:6:7:8") + ipv6test(1,"::2:3:4:5:6:7") + ipv6test(1,"::2:3:4:5:6") + ipv6test(1,"::2:3:4:5") + ipv6test(1,"::2:3:4") + ipv6test(1,"::2:3") + ipv6test(1,"::8") + ipv6test(1,"1:2:3:4:5:6::") + ipv6test(1,"1:2:3:4:5::") + ipv6test(1,"1:2:3:4::") + ipv6test(1,"1:2:3::") + ipv6test(1,"1:2::") + ipv6test(1,"1::") + ipv6test(1,"1:2:3:4:5::7:8") + ipv6test(!1,"1:2:3::4:5::7:8") // Double "::" + ipv6test(!1,"12345::6:7:8") + ipv6test(1,"1:2:3:4::7:8") + ipv6test(1,"1:2:3::7:8") + ipv6test(1,"1:2::7:8") + ipv6test(1,"1::7:8") + +// IPv4 addresses as dotted-quads + ipv6test(1,"1:2:3:4:5:6:1.2.3.4") + ipv6test(1,"1:2:3:4:5::1.2.3.4") + ipv6test(1,"1:2:3:4::1.2.3.4") + ipv6test(1,"1:2:3::1.2.3.4") + ipv6test(1,"1:2::1.2.3.4") + ipv6test(1,"1::1.2.3.4") + ipv6test(1,"1:2:3:4::5:1.2.3.4") + ipv6test(1,"1:2:3::5:1.2.3.4") + ipv6test(1,"1:2::5:1.2.3.4") + ipv6test(1,"1::5:1.2.3.4") + ipv6test(1,"1::5:11.22.33.44") + ipv6test(!1,"1::5:400.2.3.4") + ipv6test(!1,"1::5:260.2.3.4") + ipv6test(!1,"1::5:256.2.3.4") + ipv6test(!1,"1::5:1.256.3.4") + ipv6test(!1,"1::5:1.2.256.4") + ipv6test(!1,"1::5:1.2.3.256") + ipv6test(!1,"1::5:300.2.3.4") + ipv6test(!1,"1::5:1.300.3.4") + ipv6test(!1,"1::5:1.2.300.4") + ipv6test(!1,"1::5:1.2.3.300") + ipv6test(!1,"1::5:900.2.3.4") + ipv6test(!1,"1::5:1.900.3.4") + ipv6test(!1,"1::5:1.2.900.4") + ipv6test(!1,"1::5:1.2.3.900") + ipv6test(!1,"1::5:300.300.300.300") + ipv6test(!1,"1::5:3000.30.30.30") + ipv6test(!1,"1::400.2.3.4") + ipv6test(!1,"1::260.2.3.4") + ipv6test(!1,"1::256.2.3.4") + ipv6test(!1,"1::1.256.3.4") + ipv6test(!1,"1::1.2.256.4") + ipv6test(!1,"1::1.2.3.256") + ipv6test(!1,"1::300.2.3.4") + ipv6test(!1,"1::1.300.3.4") + ipv6test(!1,"1::1.2.300.4") + ipv6test(!1,"1::1.2.3.300") + ipv6test(!1,"1::900.2.3.4") + ipv6test(!1,"1::1.900.3.4") + ipv6test(!1,"1::1.2.900.4") + ipv6test(!1,"1::1.2.3.900") + ipv6test(!1,"1::300.300.300.300") + ipv6test(!1,"1::3000.30.30.30") + ipv6test(!1,"::400.2.3.4") + ipv6test(!1,"::260.2.3.4") + ipv6test(!1,"::256.2.3.4") + ipv6test(!1,"::1.256.3.4") + ipv6test(!1,"::1.2.256.4") + ipv6test(!1,"::1.2.3.256") + ipv6test(!1,"::300.2.3.4") + ipv6test(!1,"::1.300.3.4") + ipv6test(!1,"::1.2.300.4") + ipv6test(!1,"::1.2.3.300") + ipv6test(!1,"::900.2.3.4") + ipv6test(!1,"::1.900.3.4") + ipv6test(!1,"::1.2.900.4") + ipv6test(!1,"::1.2.3.900") + ipv6test(!1,"::300.300.300.300") + ipv6test(!1,"::3000.30.30.30") + ipv6test(1,"fe80::217:f2ff:254.7.237.98") + ipv6test(1,"::ffff:192.168.1.26") + ipv6test(!1,"2001:1:1:1:1:1:255Z255X255Y255") // garbage instead of "." in IPv4 + ipv6test(!1,"::ffff:192x168.1.26") // ditto + ipv6test(1,"::ffff:192.168.1.1") + ipv6test(1,"0:0:0:0:0:0:13.1.68.3")// IPv4-compatible IPv6 address, full, deprecated + ipv6test(1,"0:0:0:0:0:FFFF:129.144.52.38")// IPv4-mapped IPv6 address, full + ipv6test(1,"::13.1.68.3")// IPv4-compatible IPv6 address, compressed, deprecated + ipv6test(1,"::FFFF:129.144.52.38")// IPv4-mapped IPv6 address, compressed + ipv6test(1,"fe80:0:0:0:204:61ff:254.157.241.86") + ipv6test(1,"fe80::204:61ff:254.157.241.86") + ipv6test(1,"::ffff:12.34.56.78") + ipv6test(!1,"::ffff:2.3.4") + ipv6test(!1,"::ffff:257.1.2.3") + ipv6test(!1,"1.2.3.4") + + ipv6test(!1,"1.2.3.4:1111:2222:3333:4444::5555") // Aeron + ipv6test(!1,"1.2.3.4:1111:2222:3333::5555") + ipv6test(!1,"1.2.3.4:1111:2222::5555") + ipv6test(!1,"1.2.3.4:1111::5555") + ipv6test(!1,"1.2.3.4::5555") + ipv6test(!1,"1.2.3.4::") + +// Testing IPv4 addresses represented as dotted-quads +// Leading zero's in IPv4 addresses not allowed: some systems treat the leading "0" in ".086" as the start of an octal number +// Update: The BNF in RFC-3986 explicitly defines the dec-octet (for IPv4 addresses) not to have a leading zero + ipv6test(!1,"fe80:0000:0000:0000:0204:61ff:254.157.241.086") + ipv6test(1,"::ffff:192.0.2.128") // but this is OK, since there's a single digit + ipv6test(!1,"XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:00.00.00.00") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:000.000.000.000") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:256.256.256.256") + +// Not testing address with subnet mask +// ipv6test(1,"2001:0DB8:0000:CD30:0000:0000:0000:0000/60")// full, with prefix + // ipv6test(1,"2001:0DB8::CD30:0:0:0:0/60")// compressed, with prefix + // ipv6test(1,"2001:0DB8:0:CD30::/60")// compressed, with prefix #2 +// ipv6test(1,"::/128")// compressed, unspecified address type, non-routable +// ipv6test(1,"::1/128")// compressed, loopback address type, non-routable +// ipv6test(1,"FF00::/8")// compressed, multicast address type +// ipv6test(1,"FE80::/10")// compressed, link-local unicast, non-routable +// ipv6test(1,"FEC0::/10")// compressed, site-local unicast, deprecated +// ipv6test(!1,"124.15.6.89/60")// standard IPv4, prefix not allowed + + ipv6test(1,"fe80:0000:0000:0000:0204:61ff:fe9d:f156") + ipv6test(1,"fe80:0:0:0:204:61ff:fe9d:f156") + ipv6test(1,"fe80::204:61ff:fe9d:f156") + ipv6test(1,"::1") + ipv6test(1,"fe80::") + ipv6test(1,"fe80::1") + ipv6test(!1,":") + ipv6test(1,"::ffff:c000:280") + +// Aeron supplied these test cases + ipv6test(!1,"1111:2222:3333:4444::5555:") + ipv6test(!1,"1111:2222:3333::5555:") + ipv6test(!1,"1111:2222::5555:") + ipv6test(!1,"1111::5555:") + ipv6test(!1,"::5555:") + ipv6test(!1,":::") + ipv6test(!1,"1111:") + ipv6test(!1,":") + + ipv6test(!1,":1111:2222:3333:4444::5555") + ipv6test(!1,":1111:2222:3333::5555") + ipv6test(!1,":1111:2222::5555") + ipv6test(!1,":1111::5555") + ipv6test(!1,":::5555") + ipv6test(!1,":::") + +// Additional test cases +// from http://rt.cpan.org/Public/Bug/Display.html?id=50693 + + ipv6test(1,"2001:0db8:85a3:0000:0000:8a2e:0370:7334") + ipv6test(1,"2001:db8:85a3:0:0:8a2e:370:7334") + ipv6test(1,"2001:db8:85a3::8a2e:370:7334") + ipv6test(1,"2001:0db8:0000:0000:0000:0000:1428:57ab") + ipv6test(1,"2001:0db8:0000:0000:0000::1428:57ab") + ipv6test(1,"2001:0db8:0:0:0:0:1428:57ab") + ipv6test(1,"2001:0db8:0:0::1428:57ab") + ipv6test(1,"2001:0db8::1428:57ab") + ipv6test(1,"2001:db8::1428:57ab") + ipv6test(1,"0000:0000:0000:0000:0000:0000:0000:0001") + ipv6test(1,"::1") + ipv6test(1,"::ffff:0c22:384e") + ipv6test(1,"2001:0db8:1234:0000:0000:0000:0000:0000") + ipv6test(1,"2001:0db8:1234:ffff:ffff:ffff:ffff:ffff") + ipv6test(1,"2001:db8:a::123") + ipv6test(1,"fe80::") + + ipv6test(!1,"123") + ipv6test(!1,"ldkfj") + ipv6test(!1,"2001::FFD3::57ab") + ipv6test(!1,"2001:db8:85a3::8a2e:37023:7334") + ipv6test(!1,"2001:db8:85a3::8a2e:370k:7334") + ipv6test(!1,"1:2:3:4:5:6:7:8:9") + ipv6test(!1,"1::2::3") + ipv6test(!1,"1:::3:4:5") + ipv6test(!1,"1:2:3::4:5:6:7:8:9") + +// New from Aeron + ipv6test(1,"1111:2222:3333:4444:5555:6666:7777:8888") + ipv6test(1,"1111:2222:3333:4444:5555:6666:7777::") + ipv6test(1,"1111:2222:3333:4444:5555:6666::") + ipv6test(1,"1111:2222:3333:4444:5555::") + ipv6test(1,"1111:2222:3333:4444::") + ipv6test(1,"1111:2222:3333::") + ipv6test(1,"1111:2222::") + ipv6test(1,"1111::") +// ipv6test(1,"::") #duplicate + ipv6test(1,"1111:2222:3333:4444:5555:6666::8888") + ipv6test(1,"1111:2222:3333:4444:5555::8888") + ipv6test(1,"1111:2222:3333:4444::8888") + ipv6test(1,"1111:2222:3333::8888") + ipv6test(1,"1111:2222::8888") + ipv6test(1,"1111::8888") + ipv6test(1,"::8888") + ipv6test(1,"1111:2222:3333:4444:5555::7777:8888") + ipv6test(1,"1111:2222:3333:4444::7777:8888") + ipv6test(1,"1111:2222:3333::7777:8888") + ipv6test(1,"1111:2222::7777:8888") + ipv6test(1,"1111::7777:8888") + ipv6test(1,"::7777:8888") + ipv6test(1,"1111:2222:3333:4444::6666:7777:8888") + ipv6test(1,"1111:2222:3333::6666:7777:8888") + ipv6test(1,"1111:2222::6666:7777:8888") + ipv6test(1,"1111::6666:7777:8888") + ipv6test(1,"::6666:7777:8888") + ipv6test(1,"1111:2222:3333::5555:6666:7777:8888") + ipv6test(1,"1111:2222::5555:6666:7777:8888") + ipv6test(1,"1111::5555:6666:7777:8888") + ipv6test(1,"::5555:6666:7777:8888") + ipv6test(1,"1111:2222::4444:5555:6666:7777:8888") + ipv6test(1,"1111::4444:5555:6666:7777:8888") + ipv6test(1,"::4444:5555:6666:7777:8888") + ipv6test(1,"1111::3333:4444:5555:6666:7777:8888") + ipv6test(1,"::3333:4444:5555:6666:7777:8888") + ipv6test(1,"::2222:3333:4444:5555:6666:7777:8888") + ipv6test(1,"1111:2222:3333:4444:5555:6666:123.123.123.123") + ipv6test(1,"1111:2222:3333:4444:5555::123.123.123.123") + ipv6test(1,"1111:2222:3333:4444::123.123.123.123") + ipv6test(1,"1111:2222:3333::123.123.123.123") + ipv6test(1,"1111:2222::123.123.123.123") + ipv6test(1,"1111::123.123.123.123") + ipv6test(1,"::123.123.123.123") + ipv6test(1,"1111:2222:3333:4444::6666:123.123.123.123") + ipv6test(1,"1111:2222:3333::6666:123.123.123.123") + ipv6test(1,"1111:2222::6666:123.123.123.123") + ipv6test(1,"1111::6666:123.123.123.123") + ipv6test(1,"::6666:123.123.123.123") + ipv6test(1,"1111:2222:3333::5555:6666:123.123.123.123") + ipv6test(1,"1111:2222::5555:6666:123.123.123.123") + ipv6test(1,"1111::5555:6666:123.123.123.123") + ipv6test(1,"::5555:6666:123.123.123.123") + ipv6test(1,"1111:2222::4444:5555:6666:123.123.123.123") + ipv6test(1,"1111::4444:5555:6666:123.123.123.123") + ipv6test(1,"::4444:5555:6666:123.123.123.123") + ipv6test(1,"1111::3333:4444:5555:6666:123.123.123.123") + ipv6test(1,"::2222:3333:4444:5555:6666:123.123.123.123") + +// Playing with combinations of "0" and "::" +// NB: these are all sytactically correct, but are bad form +// because "0" adjacent to "::" should be combined into "::" + ipv6test(1,"::0:0:0:0:0:0:0") + ipv6test(1,"::0:0:0:0:0:0") + ipv6test(1,"::0:0:0:0:0") + ipv6test(1,"::0:0:0:0") + ipv6test(1,"::0:0:0") + ipv6test(1,"::0:0") + ipv6test(1,"::0") + ipv6test(1,"0:0:0:0:0:0:0::") + ipv6test(1,"0:0:0:0:0:0::") + ipv6test(1,"0:0:0:0:0::") + ipv6test(1,"0:0:0:0::") + ipv6test(1,"0:0:0::") + ipv6test(1,"0:0::") + ipv6test(1,"0::") + +// New invalid from Aeron +// Invalid data + ipv6test(!1,"XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") + +// Too many components + ipv6test(!1,"1111:2222:3333:4444:5555:6666:7777:8888:9999") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:7777:8888::") + ipv6test(!1,"::2222:3333:4444:5555:6666:7777:8888:9999") + +// Too few components + ipv6test(!1,"1111:2222:3333:4444:5555:6666:7777") + ipv6test(!1,"1111:2222:3333:4444:5555:6666") + ipv6test(!1,"1111:2222:3333:4444:5555") + ipv6test(!1,"1111:2222:3333:4444") + ipv6test(!1,"1111:2222:3333") + ipv6test(!1,"1111:2222") + ipv6test(!1,"1111") + +// Missing : + ipv6test(!1,"11112222:3333:4444:5555:6666:7777:8888") + ipv6test(!1,"1111:22223333:4444:5555:6666:7777:8888") + ipv6test(!1,"1111:2222:33334444:5555:6666:7777:8888") + ipv6test(!1,"1111:2222:3333:44445555:6666:7777:8888") + ipv6test(!1,"1111:2222:3333:4444:55556666:7777:8888") + ipv6test(!1,"1111:2222:3333:4444:5555:66667777:8888") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:77778888") + +// Missing : intended for :: + ipv6test(!1,"1111:2222:3333:4444:5555:6666:7777:8888:") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:7777:") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:") + ipv6test(!1,"1111:2222:3333:4444:5555:") + ipv6test(!1,"1111:2222:3333:4444:") + ipv6test(!1,"1111:2222:3333:") + ipv6test(!1,"1111:2222:") + ipv6test(!1,"1111:") + ipv6test(!1,":") + ipv6test(!1,":8888") + ipv6test(!1,":7777:8888") + ipv6test(!1,":6666:7777:8888") + ipv6test(!1,":5555:6666:7777:8888") + ipv6test(!1,":4444:5555:6666:7777:8888") + ipv6test(!1,":3333:4444:5555:6666:7777:8888") + ipv6test(!1,":2222:3333:4444:5555:6666:7777:8888") + ipv6test(!1,":1111:2222:3333:4444:5555:6666:7777:8888") + +// ::: + ipv6test(!1,":::2222:3333:4444:5555:6666:7777:8888") + ipv6test(!1,"1111:::3333:4444:5555:6666:7777:8888") + ipv6test(!1,"1111:2222:::4444:5555:6666:7777:8888") + ipv6test(!1,"1111:2222:3333:::5555:6666:7777:8888") + ipv6test(!1,"1111:2222:3333:4444:::6666:7777:8888") + ipv6test(!1,"1111:2222:3333:4444:5555:::7777:8888") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:::8888") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:7777:::") + +// Double ::") + ipv6test(!1,"::2222::4444:5555:6666:7777:8888") + ipv6test(!1,"::2222:3333::5555:6666:7777:8888") + ipv6test(!1,"::2222:3333:4444::6666:7777:8888") + ipv6test(!1,"::2222:3333:4444:5555::7777:8888") + ipv6test(!1,"::2222:3333:4444:5555:7777::8888") + ipv6test(!1,"::2222:3333:4444:5555:7777:8888::") + + ipv6test(!1,"1111::3333::5555:6666:7777:8888") + ipv6test(!1,"1111::3333:4444::6666:7777:8888") + ipv6test(!1,"1111::3333:4444:5555::7777:8888") + ipv6test(!1,"1111::3333:4444:5555:6666::8888") + ipv6test(!1,"1111::3333:4444:5555:6666:7777::") + + ipv6test(!1,"1111:2222::4444::6666:7777:8888") + ipv6test(!1,"1111:2222::4444:5555::7777:8888") + ipv6test(!1,"1111:2222::4444:5555:6666::8888") + ipv6test(!1,"1111:2222::4444:5555:6666:7777::") + + ipv6test(!1,"1111:2222:3333::5555::7777:8888") + ipv6test(!1,"1111:2222:3333::5555:6666::8888") + ipv6test(!1,"1111:2222:3333::5555:6666:7777::") + + ipv6test(!1,"1111:2222:3333:4444::6666::8888") + ipv6test(!1,"1111:2222:3333:4444::6666:7777::") + + ipv6test(!1,"1111:2222:3333:4444:5555::7777::") + + +// Too many components" + ipv6test(!1,"1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:7777:1.2.3.4") + ipv6test(!1,"1111:2222:3333:4444:5555:6666::1.2.3.4") + ipv6test(!1,"::2222:3333:4444:5555:6666:7777:1.2.3.4") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:1.2.3.4.5") + +// Too few components + ipv6test(!1,"1111:2222:3333:4444:5555:1.2.3.4") + ipv6test(!1,"1111:2222:3333:4444:1.2.3.4") + ipv6test(!1,"1111:2222:3333:1.2.3.4") + ipv6test(!1,"1111:2222:1.2.3.4") + ipv6test(!1,"1111:1.2.3.4") + ipv6test(!1,"1.2.3.4") + +// Missing : + ipv6test(!1,"11112222:3333:4444:5555:6666:1.2.3.4") + ipv6test(!1,"1111:22223333:4444:5555:6666:1.2.3.4") + ipv6test(!1,"1111:2222:33334444:5555:6666:1.2.3.4") + ipv6test(!1,"1111:2222:3333:44445555:6666:1.2.3.4") + ipv6test(!1,"1111:2222:3333:4444:55556666:1.2.3.4") + ipv6test(!1,"1111:2222:3333:4444:5555:66661.2.3.4") + +// Missing . + ipv6test(!1,"1111:2222:3333:4444:5555:6666:255255.255.255") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:255.255255.255") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:255.255.255255") + +// Missing : intended for :: + ipv6test(!1,":1.2.3.4") + ipv6test(!1,":6666:1.2.3.4") + ipv6test(!1,":5555:6666:1.2.3.4") + ipv6test(!1,":4444:5555:6666:1.2.3.4") + ipv6test(!1,":3333:4444:5555:6666:1.2.3.4") + ipv6test(!1,":2222:3333:4444:5555:6666:1.2.3.4") + ipv6test(!1,":1111:2222:3333:4444:5555:6666:1.2.3.4") + +// ::: + ipv6test(!1,":::2222:3333:4444:5555:6666:1.2.3.4") + ipv6test(!1,"1111:::3333:4444:5555:6666:1.2.3.4") + ipv6test(!1,"1111:2222:::4444:5555:6666:1.2.3.4") + ipv6test(!1,"1111:2222:3333:::5555:6666:1.2.3.4") + ipv6test(!1,"1111:2222:3333:4444:::6666:1.2.3.4") + ipv6test(!1,"1111:2222:3333:4444:5555:::1.2.3.4") + +// Double :: + ipv6test(!1,"::2222::4444:5555:6666:1.2.3.4") + ipv6test(!1,"::2222:3333::5555:6666:1.2.3.4") + ipv6test(!1,"::2222:3333:4444::6666:1.2.3.4") + ipv6test(!1,"::2222:3333:4444:5555::1.2.3.4") + + ipv6test(!1,"1111::3333::5555:6666:1.2.3.4") + ipv6test(!1,"1111::3333:4444::6666:1.2.3.4") + ipv6test(!1,"1111::3333:4444:5555::1.2.3.4") + + ipv6test(!1,"1111:2222::4444::6666:1.2.3.4") + ipv6test(!1,"1111:2222::4444:5555::1.2.3.4") + + ipv6test(!1,"1111:2222:3333::5555::1.2.3.4") + +// Missing parts + ipv6test(!1,"::.") + ipv6test(!1,"::..") + ipv6test(!1,"::...") + ipv6test(!1,"::1...") + ipv6test(!1,"::1.2..") + ipv6test(!1,"::1.2.3.") + ipv6test(!1,"::.2..") + ipv6test(!1,"::.2.3.") + ipv6test(!1,"::.2.3.4") + ipv6test(!1,"::..3.") + ipv6test(!1,"::..3.4") + ipv6test(!1,"::...4") + +// Extra : in front + ipv6test(!1,":1111:2222:3333:4444:5555:6666:7777::") + ipv6test(!1,":1111:2222:3333:4444:5555:6666::") + ipv6test(!1,":1111:2222:3333:4444:5555::") + ipv6test(!1,":1111:2222:3333:4444::") + ipv6test(!1,":1111:2222:3333::") + ipv6test(!1,":1111:2222::") + ipv6test(!1,":1111::") + ipv6test(!1,":::") + ipv6test(!1,":1111:2222:3333:4444:5555:6666::8888") + ipv6test(!1,":1111:2222:3333:4444:5555::8888") + ipv6test(!1,":1111:2222:3333:4444::8888") + ipv6test(!1,":1111:2222:3333::8888") + ipv6test(!1,":1111:2222::8888") + ipv6test(!1,":1111::8888") + ipv6test(!1,":::8888") + ipv6test(!1,":1111:2222:3333:4444:5555::7777:8888") + ipv6test(!1,":1111:2222:3333:4444::7777:8888") + ipv6test(!1,":1111:2222:3333::7777:8888") + ipv6test(!1,":1111:2222::7777:8888") + ipv6test(!1,":1111::7777:8888") + ipv6test(!1,":::7777:8888") + ipv6test(!1,":1111:2222:3333:4444::6666:7777:8888") + ipv6test(!1,":1111:2222:3333::6666:7777:8888") + ipv6test(!1,":1111:2222::6666:7777:8888") + ipv6test(!1,":1111::6666:7777:8888") + ipv6test(!1,":::6666:7777:8888") + ipv6test(!1,":1111:2222:3333::5555:6666:7777:8888") + ipv6test(!1,":1111:2222::5555:6666:7777:8888") + ipv6test(!1,":1111::5555:6666:7777:8888") + ipv6test(!1,":::5555:6666:7777:8888") + ipv6test(!1,":1111:2222::4444:5555:6666:7777:8888") + ipv6test(!1,":1111::4444:5555:6666:7777:8888") + ipv6test(!1,":::4444:5555:6666:7777:8888") + ipv6test(!1,":1111::3333:4444:5555:6666:7777:8888") + ipv6test(!1,":::3333:4444:5555:6666:7777:8888") + ipv6test(!1,":::2222:3333:4444:5555:6666:7777:8888") + ipv6test(!1,":1111:2222:3333:4444:5555:6666:1.2.3.4") + ipv6test(!1,":1111:2222:3333:4444:5555::1.2.3.4") + ipv6test(!1,":1111:2222:3333:4444::1.2.3.4") + ipv6test(!1,":1111:2222:3333::1.2.3.4") + ipv6test(!1,":1111:2222::1.2.3.4") + ipv6test(!1,":1111::1.2.3.4") + ipv6test(!1,":::1.2.3.4") + ipv6test(!1,":1111:2222:3333:4444::6666:1.2.3.4") + ipv6test(!1,":1111:2222:3333::6666:1.2.3.4") + ipv6test(!1,":1111:2222::6666:1.2.3.4") + ipv6test(!1,":1111::6666:1.2.3.4") + ipv6test(!1,":::6666:1.2.3.4") + ipv6test(!1,":1111:2222:3333::5555:6666:1.2.3.4") + ipv6test(!1,":1111:2222::5555:6666:1.2.3.4") + ipv6test(!1,":1111::5555:6666:1.2.3.4") + ipv6test(!1,":::5555:6666:1.2.3.4") + ipv6test(!1,":1111:2222::4444:5555:6666:1.2.3.4") + ipv6test(!1,":1111::4444:5555:6666:1.2.3.4") + ipv6test(!1,":::4444:5555:6666:1.2.3.4") + ipv6test(!1,":1111::3333:4444:5555:6666:1.2.3.4") + ipv6test(!1,":::2222:3333:4444:5555:6666:1.2.3.4") + +// Extra : at end + ipv6test(!1,"1111:2222:3333:4444:5555:6666:7777:::") + ipv6test(!1,"1111:2222:3333:4444:5555:6666:::") + ipv6test(!1,"1111:2222:3333:4444:5555:::") + ipv6test(!1,"1111:2222:3333:4444:::") + ipv6test(!1,"1111:2222:3333:::") + ipv6test(!1,"1111:2222:::") + ipv6test(!1,"1111:::") + ipv6test(!1,":::") + ipv6test(!1,"1111:2222:3333:4444:5555:6666::8888:") + ipv6test(!1,"1111:2222:3333:4444:5555::8888:") + ipv6test(!1,"1111:2222:3333:4444::8888:") + ipv6test(!1,"1111:2222:3333::8888:") + ipv6test(!1,"1111:2222::8888:") + ipv6test(!1,"1111::8888:") + ipv6test(!1,"::8888:") + ipv6test(!1,"1111:2222:3333:4444:5555::7777:8888:") + ipv6test(!1,"1111:2222:3333:4444::7777:8888:") + ipv6test(!1,"1111:2222:3333::7777:8888:") + ipv6test(!1,"1111:2222::7777:8888:") + ipv6test(!1,"1111::7777:8888:") + ipv6test(!1,"::7777:8888:") + ipv6test(!1,"1111:2222:3333:4444::6666:7777:8888:") + ipv6test(!1,"1111:2222:3333::6666:7777:8888:") + ipv6test(!1,"1111:2222::6666:7777:8888:") + ipv6test(!1,"1111::6666:7777:8888:") + ipv6test(!1,"::6666:7777:8888:") + ipv6test(!1,"1111:2222:3333::5555:6666:7777:8888:") + ipv6test(!1,"1111:2222::5555:6666:7777:8888:") + ipv6test(!1,"1111::5555:6666:7777:8888:") + ipv6test(!1,"::5555:6666:7777:8888:") + ipv6test(!1,"1111:2222::4444:5555:6666:7777:8888:") + ipv6test(!1,"1111::4444:5555:6666:7777:8888:") + ipv6test(!1,"::4444:5555:6666:7777:8888:") + ipv6test(!1,"1111::3333:4444:5555:6666:7777:8888:") + ipv6test(!1,"::3333:4444:5555:6666:7777:8888:") + ipv6test(!1,"::2222:3333:4444:5555:6666:7777:8888:") + +// Additional cases: http://crisp.tweakblogs.net/blog/2031/ipv6-validation-%28and-caveats%29.html + ipv6test(1,"0:a:b:c:d:e:f::") + ipv6test(1,"::0:a:b:c:d:e:f") // syntactically correct, but bad form (::0:... could be combined) + ipv6test(1,"a:b:c:d:e:f:0::") + ipv6test(!1,"':10.0.0.1") + +// Testing for IPv4 in IPv6 format: + ipv6test(1,"::10.0.0.1") + ipv6test(1,"::FFFF:10.0.0.1") +// NB All the regexes fail to detect somewhat suspect v4 in v6 addresses +// Leaving this test out +// ipv6test(!1,"1::1.2.3.4") + +// Testing for "%" at end of IPv6 address + ipv6test(!1,"2001:db8::%1") +}) diff --git a/target/cjs/core.cjs b/target/cjs/core.cjs index e659900..b17881c 100644 --- a/target/cjs/core.cjs +++ b/target/cjs/core.cjs @@ -338,13 +338,13 @@ var _Address = class _Address { if (!addr) throw new Error(`Invalid address: empty`); if (addr === "::") return this.create(/* @__PURE__ */ BigInt("0"), 6, addr); if (addr === "0") return this.create(/* @__PURE__ */ BigInt("0"), 4, addr); - return addr.includes(":") ? this.fromIPv6(addr) : this.fromIPv4(addr); + return addr.includes(":") ? this.fromIPv6(addr, this.strict) : this.fromIPv4(addr); } - static fromIPv6(addr) { + static fromIPv6(addr, strict) { const al = addr.length; const sep = addr.indexOf("::"); if (al > IPV6_LEN_LIM || sep !== -1 && addr.indexOf("::", sep + 1) !== -1) - throw new Error(`Invalid address: ${addr}`); + throw new Error(`Invalid address0: ${addr}`); const groups = []; let p = 0, gc = -1; while (true) { @@ -357,7 +357,10 @@ var _Address = class _Address { throw new Error(`Invalid address: ${addr}`); gc = groups.length; } else if (last && v.includes(".")) { - if (groups.length > 6 || gc === groups.length || gc === -1 && groups.length !== 6 || groups[groups.length - 1] !== 65535 || groups.slice(0, -1).some((x) => x !== 0)) throw new Error(`Invalid address: ${addr}`); + if (gc === -1 ? groups.length !== 6 : groups.length > 5) + throw new Error(`Invalid address: ${addr}`); + if (strict && (gc === groups.length || groups[groups.length - 1] !== 65535 || groups.slice(0, -1).some((x) => x !== 0))) + throw new Error(`Invalid address: ${addr}`); const long = _Address.normalizeToLong(v, true); if (long === -1) throw new Error(`Invalid address: ${addr}`); return this.create(/* @__PURE__ */ BigInt("0xffff") << /* @__PURE__ */ BigInt("32") | BigInt(long), 6, addr); @@ -369,7 +372,7 @@ var _Address = class _Address { p = i + 1; } const offset = 8 - groups.length; - if (gc === -1 ? offset !== 0 : offset < 1) throw new Error(`Invalid address: ${addr}`); + if (gc === -1 ? offset !== 0 : offset < 1) throw new Error(`Invalid address4: ${addr}`); let big = /* @__PURE__ */ BigInt("0"); for (let i = 0; i < 8; i++) { const idx = i < gc ? i : i < gc + offset ? -1 : i - offset; @@ -440,6 +443,7 @@ var _Address = class _Address { return !this.isPrivate(addr); } }; +__publicField(_Address, "strict", true); __publicField(_Address, "fromPrefixLen", (prefixlen, family) => { if (typeof prefixlen === "string" && !isDec(prefixlen)) throw new Error(`Invalid prefix: ${prefixlen}`); const len = +prefixlen | 0; diff --git a/target/dts/core.d.ts b/target/dts/core.d.ts index 4146a58..b09a240 100644 --- a/target/dts/core.d.ts +++ b/target/dts/core.d.ts @@ -26,6 +26,7 @@ export declare class Address { toLong(): number; get range(): Special | undefined; private static create; + static strict: boolean; static from(raw: Raw): Address; static mask(addr: Raw, mask: Raw): string; static subnet(addr: Raw, smask: Raw): Subnet; diff --git a/target/esm/core.mjs b/target/esm/core.mjs index 555bad1..f937557 100644 --- a/target/esm/core.mjs +++ b/target/esm/core.mjs @@ -303,13 +303,13 @@ var _Address = class _Address { if (!addr) throw new Error(`Invalid address: empty`); if (addr === "::") return this.create(/* @__PURE__ */ BigInt("0"), 6, addr); if (addr === "0") return this.create(/* @__PURE__ */ BigInt("0"), 4, addr); - return addr.includes(":") ? this.fromIPv6(addr) : this.fromIPv4(addr); + return addr.includes(":") ? this.fromIPv6(addr, this.strict) : this.fromIPv4(addr); } - static fromIPv6(addr) { + static fromIPv6(addr, strict) { const al = addr.length; const sep = addr.indexOf("::"); if (al > IPV6_LEN_LIM || sep !== -1 && addr.indexOf("::", sep + 1) !== -1) - throw new Error(`Invalid address: ${addr}`); + throw new Error(`Invalid address0: ${addr}`); const groups = []; let p = 0, gc = -1; while (true) { @@ -322,7 +322,10 @@ var _Address = class _Address { throw new Error(`Invalid address: ${addr}`); gc = groups.length; } else if (last && v.includes(".")) { - if (groups.length > 6 || gc === groups.length || gc === -1 && groups.length !== 6 || groups[groups.length - 1] !== 65535 || groups.slice(0, -1).some((x) => x !== 0)) throw new Error(`Invalid address: ${addr}`); + if (gc === -1 ? groups.length !== 6 : groups.length > 5) + throw new Error(`Invalid address: ${addr}`); + if (strict && (gc === groups.length || groups[groups.length - 1] !== 65535 || groups.slice(0, -1).some((x) => x !== 0))) + throw new Error(`Invalid address: ${addr}`); const long = _Address.normalizeToLong(v, true); if (long === -1) throw new Error(`Invalid address: ${addr}`); return this.create(/* @__PURE__ */ BigInt("0xffff") << /* @__PURE__ */ BigInt("32") | BigInt(long), 6, addr); @@ -334,7 +337,7 @@ var _Address = class _Address { p = i + 1; } const offset = 8 - groups.length; - if (gc === -1 ? offset !== 0 : offset < 1) throw new Error(`Invalid address: ${addr}`); + if (gc === -1 ? offset !== 0 : offset < 1) throw new Error(`Invalid address4: ${addr}`); let big = /* @__PURE__ */ BigInt("0"); for (let i = 0; i < 8; i++) { const idx = i < gc ? i : i < gc + offset ? -1 : i - offset; @@ -405,6 +408,7 @@ var _Address = class _Address { return !this.isPrivate(addr); } }; +__publicField(_Address, "strict", true); __publicField(_Address, "fromPrefixLen", (prefixlen, family) => { if (typeof prefixlen === "string" && !isDec(prefixlen)) throw new Error(`Invalid prefix: ${prefixlen}`); const len = +prefixlen | 0;