1use std::sync::OnceLock;
4
5use regex::Regex;
6
7const MAX_IPV4_LENGTH: usize = 15;
8const MAX_IPV6_LENGTH: usize = 45;
9
10const V4: &str = r#"(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}"#;
11
12fn v6() -> &'static str {
13 static PATTERN: OnceLock<String> = OnceLock::new();
14 PATTERN.get_or_init(|| {
15 const V6_SEG: &str = r#"[a-fA-F\d]{1,4}"#;
16 #[rustfmt::skip]
17 let buf = [
18 "(?:",
19 "(?:", V6_SEG, ":){7}(?:", V6_SEG, "|:)|", "(?:", V6_SEG, ":){6}(?:", V4, "|:", V6_SEG, "|:)|", "(?:", V6_SEG, ":){5}(?::", V4, "|(?::", V6_SEG, "){1,2}|:)|", "(?:", V6_SEG, ":){4}(?:(?::", V6_SEG, "){0,1}:", V4, "|(?::", V6_SEG, "){1,3}|:)|", "(?:", V6_SEG, ":){3}(?:(?::", V6_SEG, "){0,2}:", V4, "|(?::", V6_SEG, "){1,4}|:)|", "(?:", V6_SEG, ":){2}(?:(?::", V6_SEG, "){0,3}:", V4, "|(?::", V6_SEG, "){1,5}|:)|", "(?:", V6_SEG, ":){1}(?:(?::", V6_SEG, "){0,4}:", V4, "|(?::", V6_SEG, "){1,6}|:)|", "(?::(?:(?::", V6_SEG, "){0,5}:", V4, "|(?::", V6_SEG, "){1,7}|:))", ")(?:%[0-9a-zA-Z]{1,})?" ];
29 buf.join("").to_owned()
30 })
31}
32
33fn ip_regex() -> &'static Regex {
34 static REGEX: OnceLock<Regex> = OnceLock::new();
35 REGEX.get_or_init(|| Regex::new(format!("(?:^{}$)|(?:^{}$)", V4, v6()).as_str()).unwrap())
36}
37
38fn ipv4_regex() -> &'static Regex {
39 static REGEX: OnceLock<Regex> = OnceLock::new();
40 REGEX.get_or_init(|| Regex::new(format!("^{}$", V4).as_str()).unwrap())
41}
42
43fn ipv6_regex() -> &'static Regex {
44 static REGEX: OnceLock<Regex> = OnceLock::new();
45 REGEX.get_or_init(|| Regex::new(format!("^{}$", v6()).as_str()).unwrap())
46}
47
48pub fn is_ip(string: &str) -> bool {
50 if string.len() > MAX_IPV6_LENGTH {
51 return false;
52 }
53
54 ip_regex().is_match(string)
55}
56
57pub fn is_ipv4(string: &str) -> bool {
59 if string.len() > MAX_IPV4_LENGTH {
60 return false;
61 }
62
63 ipv4_regex().is_match(string)
64}
65
66pub fn is_ipv6(string: &str) -> bool {
68 if string.len() > MAX_IPV6_LENGTH {
69 return false;
70 }
71
72 ipv6_regex().is_match(string)
73}
74
75pub fn ip_version(string: &str) -> Option<u8> {
78 if is_ipv6(string) {
79 return Some(6);
80 }
81
82 if is_ipv4(string) {
83 return Some(4);
84 }
85
86 None
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 const TEST_V4: [&str; 16] = [
94 "0.0.0.0",
95 "8.8.8.8",
96 "127.0.0.1",
97 "100.100.100.100",
98 "192.168.0.1",
99 "18.101.25.153",
100 "123.23.34.2",
101 "172.26.168.134",
102 "212.58.241.131",
103 "128.0.0.0",
104 "23.71.254.72",
105 "223.255.255.255",
106 "192.0.2.235",
107 "99.198.122.146",
108 "46.51.197.88",
109 "173.194.34.134",
110 ];
111
112 const TEST_V4NOT: [&str; 10] = [
113 ".100.100.100.100",
114 "100..100.100.100.",
115 "100.100.100.100.",
116 "999.999.999.999",
117 "256.256.256.256",
118 "256.100.100.100.100",
119 "123.123.123",
120 "http://123.123.123",
121 "1000.2.3.4",
122 "999.2.3.4",
123 ];
124
125 const TEST_V6: [&str; 128] = [
126 "::",
127 "1::",
128 "::1",
129 "1::8",
130 "1::7:8",
131 "1:2:3:4:5:6:7:8",
132 "1:2:3:4:5:6::8",
133 "1:2:3:4:5:6:7::",
134 "1:2:3:4:5::7:8",
135 "1:2:3:4:5::8",
136 "1:2:3::8",
137 "1::4:5:6:7:8",
138 "1::6:7:8",
139 "1::3:4:5:6:7:8",
140 "1:2:3:4::6:7:8",
141 "1:2::4:5:6:7:8",
142 "::2:3:4:5:6:7:8",
143 "1:2::8",
144 "2001:0000:1234:0000:0000:C1C0:ABCD:0876",
145 "3ffe:0b00:0000:0000:0001:0000:0000:000a",
146 "FF02:0000:0000:0000:0000:0000:0000:0001",
147 "0000:0000:0000:0000:0000:0000:0000:0001",
148 "0000:0000:0000:0000:0000:0000:0000:0000",
149 "::ffff:192.168.1.26",
150 "2::10",
151 "ff02::1",
152 "fe80::",
153 "2002::",
154 "2001:db8::",
155 "2001:0db8:1234::",
156 "::ffff:0:0",
157 "::ffff:192.168.1.1",
158 "1:2:3:4::8",
159 "1::2:3:4:5:6:7",
160 "1::2:3:4:5:6",
161 "1::2:3:4:5",
162 "1::2:3:4",
163 "1::2:3",
164 "::2:3:4:5:6:7",
165 "::2:3:4:5:6",
166 "::2:3:4:5",
167 "::2:3:4",
168 "::2:3",
169 "::8",
170 "1:2:3:4:5:6::",
171 "1:2:3:4:5::",
172 "1:2:3:4::",
173 "1:2:3::",
174 "1:2::",
175 "1:2:3:4::7:8",
176 "1:2:3::7:8",
177 "1:2::7:8",
178 "1:2:3:4:5:6:1.2.3.4",
179 "1:2:3:4:5::1.2.3.4",
180 "1:2:3:4::1.2.3.4",
181 "1:2:3::1.2.3.4",
182 "1:2::1.2.3.4",
183 "1::1.2.3.4",
184 "1:2:3:4::5:1.2.3.4",
185 "1:2:3::5:1.2.3.4",
186 "1:2::5:1.2.3.4",
187 "1::5:1.2.3.4",
188 "1::5:11.22.33.44",
189 "fe80::217:f2ff:254.7.237.98",
190 "fe80::217:f2ff:fe07:ed62",
191 "2001:DB8:0:0:8:800:200C:417A",
192 "FF01:0:0:0:0:0:0:101",
193 "0:0:0:0:0:0:0:1",
194 "0:0:0:0:0:0:0:0",
195 "2001:DB8::8:800:200C:417A",
196 "FF01::101",
197 "0:0:0:0:0:0:13.1.68.3",
198 "0:0:0:0:0:FFFF:129.144.52.38",
199 "::13.1.68.3",
200 "::FFFF:129.144.52.38",
201 "fe80:0000:0000:0000:0204:61ff:fe9d:f156",
202 "fe80:0:0:0:204:61ff:fe9d:f156",
203 "fe80::204:61ff:fe9d:f156",
204 "fe80:0:0:0:204:61ff:254.157.241.86",
205 "fe80::204:61ff:254.157.241.86",
206 "fe80::1",
207 "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
208 "2001:db8:85a3:0:0:8a2e:370:7334",
209 "2001:db8:85a3::8a2e:370:7334",
210 "2001:0db8:0000:0000:0000:0000:1428:57ab",
211 "2001:0db8:0000:0000:0000::1428:57ab",
212 "2001:0db8:0:0:0:0:1428:57ab",
213 "2001:0db8:0:0::1428:57ab",
214 "2001:0db8::1428:57ab",
215 "2001:db8::1428:57ab",
216 "::ffff:12.34.56.78",
217 "::ffff:0c22:384e",
218 "2001:0db8:1234:0000:0000:0000:0000:0000",
219 "2001:0db8:1234:ffff:ffff:ffff:ffff:ffff",
220 "2001:db8:a::123",
221 "::ffff:192.0.2.128",
222 "::ffff:c000:280",
223 "a:b:c:d:e:f:f1:f2",
224 "a:b:c::d:e:f:f1",
225 "a:b:c::d:e:f",
226 "a:b:c::d:e",
227 "a:b:c::d",
228 "::a",
229 "::a:b:c",
230 "::a:b:c:d:e:f:f1",
231 "a::",
232 "a:b:c::",
233 "a:b:c:d:e:f:f1::",
234 "a:bb:ccc:dddd:000e:00f:0f::",
235 "0:a:0:a:0:0:0:a",
236 "0:a:0:0:a:0:0:a",
237 "2001:db8:1:1:1:1:0:0",
238 "2001:db8:1:1:1:0:0:0",
239 "2001:db8:1:1:0:0:0:0",
240 "2001:db8:1:0:0:0:0:0",
241 "2001:db8:0:0:0:0:0:0",
242 "2001:0:0:0:0:0:0:0",
243 "A:BB:CCC:DDDD:000E:00F:0F::",
244 "0:0:0:0:0:0:0:a",
245 "0:0:0:0:a:0:0:0",
246 "0:0:0:a:0:0:0:0",
247 "a:0:0:a:0:0:a:a",
248 "a:0:0:a:0:0:0:a",
249 "a:0:0:0:a:0:0:a",
250 "a:0:0:0:a:0:0:0",
251 "a:0:0:0:0:0:0:0",
252 "fe80::7:8%eth0",
253 "fe80::7:8%1",
254 ];
255
256 const TEST_V6NOT: [&str; 96] = [
257 "",
258 "1:",
259 ":1",
260 "11:36:12",
261 "02001:0000:1234:0000:0000:C1C0:ABCD:0876",
262 "2001:0000:1234:0000:00001:C1C0:ABCD:0876",
263 "2001:0000:1234: 0000:0000:C1C0:ABCD:0876",
264 "2001:1:1:1:1:1:255Z255X255Y255",
265 "3ffe:0b00:0000:0001:0000:0000:000a",
266 "FF02:0000:0000:0000:0000:0000:0000:0000:0001",
267 "3ffe:b00::1::a",
268 "::1111:2222:3333:4444:5555:6666::",
269 "1:2:3::4:5::7:8",
270 "12345::6:7:8",
271 "1::5:400.2.3.4",
272 "1::5:260.2.3.4",
273 "1::5:256.2.3.4",
274 "1::5:1.256.3.4",
275 "1::5:1.2.256.4",
276 "1::5:1.2.3.256",
277 "1::5:300.2.3.4",
278 "1::5:1.300.3.4",
279 "1::5:1.2.300.4",
280 "1::5:1.2.3.300",
281 "1::5:900.2.3.4",
282 "1::5:1.900.3.4",
283 "1::5:1.2.900.4",
284 "1::5:1.2.3.900",
285 "1::5:300.300.300.300",
286 "1::5:3000.30.30.30",
287 "1::400.2.3.4",
288 "1::260.2.3.4",
289 "1::256.2.3.4",
290 "1::1.256.3.4",
291 "1::1.2.256.4",
292 "1::1.2.3.256",
293 "1::300.2.3.4",
294 "1::1.300.3.4",
295 "1::1.2.300.4",
296 "1::1.2.3.300",
297 "1::900.2.3.4",
298 "1::1.900.3.4",
299 "1::1.2.900.4",
300 "1::1.2.3.900",
301 "1::300.300.300.300",
302 "1::3000.30.30.30",
303 "::400.2.3.4",
304 "::260.2.3.4",
305 "::256.2.3.4",
306 "::1.256.3.4",
307 "::1.2.256.4",
308 "::1.2.3.256",
309 "::300.2.3.4",
310 "::1.300.3.4",
311 "::1.2.300.4",
312 "::1.2.3.300",
313 "::900.2.3.4",
314 "::1.900.3.4",
315 "::1.2.900.4",
316 "::1.2.3.900",
317 "::300.300.300.300",
318 "::3000.30.30.30",
319 "2001:DB8:0:0:8:800:200C:417A:221",
320 "FF01::101::2",
321 "1111:2222:3333:4444::5555:",
322 "1111:2222:3333::5555:",
323 "1111:2222::5555:",
324 "1111::5555:",
325 "::5555:",
326 ":::",
327 "1111:",
328 ":",
329 ":1111:2222:3333:4444::5555",
330 ":1111:2222:3333::5555",
331 ":1111:2222::5555",
332 ":1111::5555",
333 ":::5555",
334 "1.2.3.4:1111:2222:3333:4444::5555",
335 "1.2.3.4:1111:2222:3333::5555",
336 "1.2.3.4:1111:2222::5555",
337 "1.2.3.4:1111::5555",
338 "1.2.3.4::5555",
339 "1.2.3.4::",
340 "fe80:0000:0000:0000:0204:61ff:254.157.241.086",
341 "123",
342 "ldkfj",
343 "2001::FFD3::57ab",
344 "2001:db8:85a3::8a2e:37023:7334",
345 "2001:db8:85a3::8a2e:370k:7334",
346 "1:2:3:4:5:6:7:8:9",
347 "1::2::3",
348 "1:::3:4:5",
349 "1:2:3::4:5:6:7:8:9",
350 "::ffff:2.3.4",
351 "::ffff:257.1.2.3",
352 "::ffff:12345678901234567890.1.26",
353 ];
354
355 #[test]
356 fn test_ip_regex() {
357 for fixture in TEST_V4 {
358 assert_eq!(ip_regex().is_match(fixture), true);
359 }
360
361 for fixture in TEST_V4NOT {
362 assert_eq!(ip_regex().is_match(fixture), false);
363 }
364
365 for fixture in TEST_V6 {
366 assert_eq!(ip_regex().is_match(fixture), true);
367 }
368
369 for fixture in TEST_V6NOT {
370 assert_eq!(ip_regex().is_match(fixture), false);
371 }
372 }
373
374 #[test]
375 fn test_ipv4_regex() {
376 for fixture in TEST_V4 {
377 assert_eq!(ipv4_regex().is_match(fixture), true);
378 }
379
380 for fixture in TEST_V4NOT {
381 assert_eq!(ipv4_regex().is_match(fixture), false);
382 }
383 }
384
385 #[test]
386 fn test_ipv6_regex() {
387 for fixture in TEST_V6 {
388 assert_eq!(ipv6_regex().is_match(fixture), true);
389 }
390
391 for fixture in TEST_V6NOT {
392 assert_eq!(ipv6_regex().is_match(fixture), false);
393 }
394 }
395
396 #[test]
397 fn test_ip() {
398 assert_eq!(is_ip("192.168.0.1"), true);
399 assert_eq!(is_ip("1:2:3:4:5:6:7:8"), true);
400 assert_eq!(is_ip("::1"), true);
401 assert_eq!(is_ip("2001:0dc5:72a3:0000:0000:802e:3370:73E4"), true);
402 }
403
404 #[test]
405 fn test_ipv4() {
406 assert_eq!(is_ipv4("192.168.0.1"), true);
407 assert_eq!(is_ipv4("123.123.123.1234"), false);
408 assert_eq!(is_ipv4("123.123.123.12345"), false);
409 }
410
411 #[test]
412 fn test_ipv6() {
413 assert_eq!(is_ipv6("1:2:3:4:5:6:7:8"), true);
414 assert_eq!(is_ipv6("::1"), true);
415 assert_eq!(is_ipv6("0000:0000:0000:0000:0000:0000:0000:0000"), true);
416 assert_eq!(is_ipv6("0000:0000:0000:0000:0000:ffff:192.168.100.228"), true);
417 assert_eq!(is_ipv6("2001:0dc5:72a3:0000:0000:802e:3370:73E4"), true);
418 assert_eq!(is_ipv6("2001:0dc5:72a3:0000:0000:802e:3370:73E4XXXXXXXX"), false);
419 }
420
421 #[test]
422 fn test_ip_version() {
423 assert_eq!(ip_version("192.168.0.1"), Some(4));
424 assert_eq!(ip_version("1:2:3:4:5:6:7:8"), Some(6));
425 assert_eq!(ip_version("abc"), None);
426 assert_eq!(ip_version("123.123.123.12345"), None);
427 }
428}