Skip to content

Commit fa28884

Browse files
committed
feat: add strict mode switcher
1 parent 31526e4 commit fa28884

8 files changed

Lines changed: 663 additions & 26 deletions

File tree

.size-limit.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"LICENSE",
1010
"package-main.json"
1111
],
12-
"limit": "60.40 kB",
12+
"limit": "61.052 kB",
1313
"brotli": false,
1414
"gzip": false
1515
},
@@ -18,7 +18,7 @@
1818
"path": [
1919
"target/*/core.*"
2020
],
21-
"limit": "41.25 kB",
21+
"limit": "41.55 kB",
2222
"brotli": false,
2323
"gzip": false
2424
},
@@ -32,15 +32,15 @@
3232
"LICENSE",
3333
"package-main.json"
3434
],
35-
"limit": "18.30 kB",
35+
"limit": "18.55 kB",
3636
"gzip": true
3737
},
3838
{
3939
"name": "cjs",
4040
"path": [
4141
"target/cjs"
4242
],
43-
"limit": "25.40 kB",
43+
"limit": "25.55 kB",
4444
"brotli": false,
4545
"gzip": false
4646
},
@@ -49,14 +49,14 @@
4949
"path": [
5050
"target/esm"
5151
],
52-
"limit": "21.00 kB",
52+
"limit": "21.15 kB",
5353
"brotli": false,
5454
"gzip": false
5555
},
5656
{
5757
"name": "libdefs",
5858
"path": "target/dts",
59-
"limit": "7.15 kB",
59+
"limit": "7.16 kB",
6060
"brotli": false,
6161
"gzip": false
6262
}

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ Temporary workaround to avoid refactoring is using `overrides` / `resolutions` i
3131

3232
Browser-compatible core build is available as `@webpod/ip/core`: it omits `node:os` dependency and polyfills the `Buffer` API.
3333

34+
### Strict mode
35+
By default, the library is in the `strict` mode that rejects non-canonical embedded IPv4 in IPv6. Switch to legacy flow if needed:
36+
37+
```ts
38+
import { Address } from '@webpod/ip'
39+
40+
Address.from('::ffff:5.6.7.8') // ok
41+
Address.from('1:2:3:4::5.6.7.8') // throws
42+
43+
Address.strict = false
44+
Address.from('1:2:3:4::5.6.7.8') // now it's fine
45+
```
46+
3447
## Usage
3548
The API is fully compatible with the latest `ip@2.0.1` but enforces stricter validations. See [coherence.md](./COHERENCE.md) for details.
3649

src/main/ts/core.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ export class Address {
157157
return o
158158
}
159159

160+
static strict = true
161+
160162
static from(raw: Raw): Address {
161163
if (raw instanceof Address) return this.create(raw.big, raw.family, raw.raw)
162164
if (typeof raw === 'string') return this.fromString(raw.toLowerCase())
@@ -334,18 +336,18 @@ export class Address {
334336
if (addr === '0') return this.create(0n, 4, addr)
335337

336338
return addr.includes(':')
337-
? this.fromIPv6(addr)
339+
? this.fromIPv6(addr, this.strict)
338340
: this.fromIPv4(addr)
339341
}
340342

341-
private static fromIPv6(addr: string): Address {
343+
private static fromIPv6(addr: string, strict?: boolean): Address {
342344
const al = addr.length
343345
const sep = addr.indexOf('::')
344346
if (
345347
al > IPV6_LEN_LIM ||
346348
sep !== -1 && addr.indexOf('::', sep + 1) !== -1 // only one '::' allowed
347349
)
348-
throw new Error(`Invalid address: ${addr}`)
350+
throw new Error(`Invalid address0: ${addr}`)
349351

350352
const groups: number[] = []
351353
let p = 0, gc = -1
@@ -360,15 +362,16 @@ export class Address {
360362
if (sep === -1 || (end !== sep && end !== sep + 1 + +last))
361363
throw new Error(`Invalid address: ${addr}`)
362364
gc = groups.length
363-
} else if (last && v.includes('.')) {
364-
// embedded IPv4
365-
if (
366-
groups.length > 6 ||
365+
} else if (last && v.includes('.')) { // embedded IPv4
366+
if (gc === -1 ? groups.length !== 6 : groups.length > 5)
367+
throw new Error(`Invalid address: ${addr}`)
368+
369+
if (strict && (
367370
gc === groups.length ||
368-
(gc === -1 && groups.length !== 6) ||
369371
groups[groups.length - 1] !== 0xffff ||
370372
groups.slice(0, -1).some(x => x !== 0)
371-
) throw new Error(`Invalid address: ${addr}`)
373+
))
374+
throw new Error(`Invalid address: ${addr}`)
372375

373376
const long = Address.normalizeToLong(v, true)
374377
if (long === -1) throw new Error(`Invalid address: ${addr}`)
@@ -382,7 +385,7 @@ export class Address {
382385
p = i + 1
383386
}
384387
const offset = 8 - groups.length
385-
if (gc === -1 ? offset !== 0 : offset < 1) throw new Error(`Invalid address: ${addr}`)
388+
if (gc === -1 ? offset !== 0 : offset < 1) throw new Error(`Invalid address4: ${addr}`)
386389

387390
let big = 0n
388391
for (let i = 0; i < 8; i++) {

src/test/js/export.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ describe('core', () => {
99
assert.equal(typeof core.Address, 'function', 'core.Address')
1010
assert.equal(typeof core.Address.fromPrefixLen, 'function', 'core.Address.fromPrefixLen')
1111
assert.equal(typeof core.Address.parseCidr, 'function', 'core.Address.parseCidr')
12+
assert.equal(typeof core.Address.strict, 'boolean', 'core.Address.strict')
1213
assert.equal(typeof core.cidr, 'function', 'core.cidr')
1314
assert.equal(typeof core.cidrSubnet, 'function', 'core.cidrSubnet')
1415
assert.equal(typeof core.fromLong, 'function', 'core.fromLong')
@@ -40,6 +41,7 @@ describe('index', () => {
4041
assert.equal(typeof index.Address, 'function', 'index.Address')
4142
assert.equal(typeof index.Address.fromPrefixLen, 'function', 'index.Address.fromPrefixLen')
4243
assert.equal(typeof index.Address.parseCidr, 'function', 'index.Address.parseCidr')
44+
assert.equal(typeof index.Address.strict, 'boolean', 'index.Address.strict')
4345
assert.equal(typeof index.address, 'function', 'index.address')
4446
assert.equal(typeof index.addresses, 'function', 'index.addresses')
4547
assert.equal(typeof index.cidr, 'function', 'index.cidr')

0 commit comments

Comments
 (0)