Skip to content

Commit 2ef84e4

Browse files
fix(isIP): validator patterns for IPv4 and IPv6 RegExp formats (#1632)
* fix: πŸ› ipv4/ipv6 validators Provide a more strict regular expression patterns for ipv4 and ipv6 formats. βœ… Closes: #1626 * test: πŸ’ ipv4/ipv6 unit tests Extend upon existing IP validator unit test, valid examples. Provide IPv6 test addresses including `%`. βœ… Closes: #1626
1 parent 67a200d commit 2ef84e4

2 files changed

Lines changed: 47 additions & 70 deletions

File tree

β€Žsrc/lib/isIP.jsβ€Ž

Lines changed: 21 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -28,86 +28,37 @@ import assertString from './util/assertString';
2828
where the interface "ne0" belongs to the 1st link, "pvc1.3" belongs
2929
to the 5th link, and "interface10" belongs to the 10th organization.
3030
* * */
31-
const ipv4Maybe = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
32-
const ipv6Block = /^[0-9A-F]{1,4}$/i;
31+
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
32+
const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
33+
const IPv4AddressRegExp = new RegExp(`^${IPv4AddressFormat}$`);
34+
35+
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
36+
const IPv6AddressRegExp = new RegExp('^(' +
37+
`(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
38+
`(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
39+
`(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
40+
`(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
41+
`(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
42+
`(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
43+
`(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
44+
`(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
45+
')(%[0-9a-zA-Z-.:]{1,})?$');
3346

3447
export default function isIP(str, version = '') {
3548
assertString(str);
3649
version = String(version);
3750
if (!version) {
3851
return isIP(str, 4) || isIP(str, 6);
39-
} else if (version === '4') {
40-
if (!ipv4Maybe.test(str)) {
52+
}
53+
if (version === '4') {
54+
if (!IPv4AddressRegExp.test(str)) {
4155
return false;
4256
}
4357
const parts = str.split('.').sort((a, b) => a - b);
4458
return parts[3] <= 255;
45-
} else if (version === '6') {
46-
let addressAndZone = [str];
47-
// ipv6 addresses could have scoped architecture
48-
// according to https://tools.ietf.org/html/rfc4007#section-11
49-
if (str.includes('%')) {
50-
addressAndZone = str.split('%');
51-
if (addressAndZone.length !== 2) {
52-
// it must be just two parts
53-
return false;
54-
}
55-
if (!addressAndZone[0].includes(':')) {
56-
// the first part must be the address
57-
return false;
58-
}
59-
60-
if (addressAndZone[1] === '') {
61-
// the second part must not be empty
62-
return false;
63-
}
64-
}
65-
66-
const blocks = addressAndZone[0].split(':');
67-
let foundOmissionBlock = false; // marker to indicate ::
68-
69-
// At least some OS accept the last 32 bits of an IPv6 address
70-
// (i.e. 2 of the blocks) in IPv4 notation, and RFC 3493 says
71-
// that '::ffff:a.b.c.d' is valid for IPv4-mapped IPv6 addresses,
72-
// and '::a.b.c.d' is deprecated, but also valid.
73-
const foundIPv4TransitionBlock = isIP(blocks[blocks.length - 1], 4);
74-
const expectedNumberOfBlocks = foundIPv4TransitionBlock ? 7 : 8;
75-
76-
if (blocks.length > expectedNumberOfBlocks) {
77-
return false;
78-
}
79-
// initial or final ::
80-
if (str === '::') {
81-
return true;
82-
} else if (str.substr(0, 2) === '::') {
83-
blocks.shift();
84-
blocks.shift();
85-
foundOmissionBlock = true;
86-
} else if (str.substr(str.length - 2) === '::') {
87-
blocks.pop();
88-
blocks.pop();
89-
foundOmissionBlock = true;
90-
}
91-
92-
for (let i = 0; i < blocks.length; ++i) {
93-
// test for a :: which can not be at the string start/end
94-
// since those cases have been handled above
95-
if (blocks[i] === '' && i > 0 && i < blocks.length - 1) {
96-
if (foundOmissionBlock) {
97-
return false; // multiple :: in address
98-
}
99-
foundOmissionBlock = true;
100-
} else if (foundIPv4TransitionBlock && i === blocks.length - 1) {
101-
// it has been checked before that the last
102-
// block is a valid IPv4 address
103-
} else if (!ipv6Block.test(blocks[i])) {
104-
return false;
105-
}
106-
}
107-
if (foundOmissionBlock) {
108-
return blocks.length >= 1;
109-
}
110-
return blocks.length === expectedNumberOfBlocks;
59+
}
60+
if (version === '6') {
61+
return !!IPv6AddressRegExp.test(str);
11162
}
11263
return false;
11364
}

β€Žtest/validators.jsβ€Ž

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,7 @@ describe('Validators', () => {
771771
'1.2.3.4',
772772
'::1',
773773
'2001:db8:0000:1:1:1:1:1',
774+
'2001:db8:3:4::192.0.2.33',
774775
'2001:41d0:2:a141::1',
775776
'::ffff:127.0.0.1',
776777
'::0000',
@@ -779,8 +780,33 @@ describe('Validators', () => {
779780
'1111:1:1:1:1:1:1:1',
780781
'fe80::a6db:30ff:fe98:e946',
781782
'::',
783+
'::8',
782784
'::ffff:127.0.0.1',
785+
'::ffff:255.255.255.255',
786+
'::ffff:0:255.255.255.255',
787+
'::2:3:4:5:6:7:8',
788+
'::255.255.255.255',
783789
'0:0:0:0:0:ffff:127.0.0.1',
790+
'1:2:3:4:5:6:7::',
791+
'1:2:3:4:5:6::8',
792+
'1::7:8',
793+
'1:2:3:4:5::7:8',
794+
'1:2:3:4:5::8',
795+
'1::6:7:8',
796+
'1:2:3:4::6:7:8',
797+
'1:2:3:4::8',
798+
'1::5:6:7:8',
799+
'1:2:3::5:6:7:8',
800+
'1:2:3::8',
801+
'1::4:5:6:7:8',
802+
'1:2::4:5:6:7:8',
803+
'1:2::8',
804+
'1::3:4:5:6:7:8',
805+
'1::8',
806+
'fe80::7:8%eth0',
807+
'fe80::7:8%1',
808+
'64:ff9b::192.0.2.33',
809+
'0:0:0:0:0:0:10.0.0.1',
784810
],
785811
invalid: [
786812
'abc',

0 commit comments

Comments
Β (0)