1"""IP Address."""
2
3# standard
4from ipaddress import (
5 AddressValueError,
6 IPv4Address,
7 IPv4Network,
8 IPv6Address,
9 IPv6Network,
10 NetmaskValueError,
11)
12import re
13from typing import Optional
14
15# local
16from .utils import validator
17
18
19def _check_private_ip(value: str, is_private: Optional[bool]):
20 if is_private is None:
21 return True
22 if (
23 any(
24 value.startswith(l_bit)
25 for l_bit in {
26 "10.", # private
27 "192.168.", # private
28 "169.254.", # link-local
29 "127.", # localhost
30 "0.0.0.0", # loopback #nosec
31 }
32 )
33 or re.match(r"^172\.(?:1[6-9]|2\d|3[0-1])\.", value) # private
34 or re.match(r"^(?:22[4-9]|23[0-9]|24[0-9]|25[0-5])\.", value) # broadcast
35 ):
36 return is_private
37
38 return not is_private
39
40
41@validator
42def ipv4(
43 value: str,
44 /,
45 *,
46 cidr: bool = True,
47 strict: bool = False,
48 private: Optional[bool] = None,
49 host_bit: bool = True,
50):
51 """Returns whether a given value is a valid IPv4 address.
52
53 From Python version 3.9.5 leading zeros are no longer tolerated
54 and are treated as an error. The initial version of ipv4 validator
55 was inspired from [WTForms IPAddress validator][1].
56
57 [1]: https://github.com/wtforms/wtforms/blob/master/src/wtforms/validators.py
58
59 Examples:
60 >>> ipv4('123.0.0.7')
61 True
62 >>> ipv4('1.1.1.1/8')
63 True
64 >>> ipv4('900.80.70.11')
65 ValidationError(func=ipv4, args={'value': '900.80.70.11'})
66
67 Args:
68 value:
69 IP address string to validate.
70 cidr:
71 IP address string may contain CIDR notation.
72 strict:
73 IP address string is strictly in CIDR notation.
74 private:
75 IP address is public if `False`, private/local/loopback/broadcast if `True`.
76 host_bit:
77 If `False` and host bits (along with network bits) _are_ set in the supplied
78 address, this function raises a validation error. ref [IPv4Network][2].
79 [2]: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Network
80
81 Returns:
82 (Literal[True]): If `value` is a valid IPv4 address.
83 (ValidationError): If `value` is an invalid IPv4 address.
84 """
85 if not value:
86 return False
87 try:
88 if cidr:
89 if strict and value.count("/") != 1:
90 raise ValueError("IPv4 address was expected in CIDR notation")
91 return IPv4Network(value, strict=not host_bit) and _check_private_ip(value, private)
92 return IPv4Address(value) and _check_private_ip(value, private)
93 except (ValueError, AddressValueError, NetmaskValueError):
94 return False
95
96
97@validator
98def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False, host_bit: bool = True):
99 """Returns if a given value is a valid IPv6 address.
100
101 Including IPv4-mapped IPv6 addresses. The initial version of ipv6 validator
102 was inspired from [WTForms IPAddress validator][1].
103
104 [1]: https://github.com/wtforms/wtforms/blob/master/src/wtforms/validators.py
105
106 Examples:
107 >>> ipv6('::ffff:192.0.2.128')
108 True
109 >>> ipv6('::1/128')
110 True
111 >>> ipv6('abc.0.0.1')
112 ValidationError(func=ipv6, args={'value': 'abc.0.0.1'})
113
114 Args:
115 value:
116 IP address string to validate.
117 cidr:
118 IP address string may contain CIDR annotation.
119 strict:
120 IP address string is strictly in CIDR notation.
121 host_bit:
122 If `False` and host bits (along with network bits) _are_ set in the supplied
123 address, this function raises a validation error. ref [IPv6Network][2].
124 [2]: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv6Network
125
126 Returns:
127 (Literal[True]): If `value` is a valid IPv6 address.
128 (ValidationError): If `value` is an invalid IPv6 address.
129 """
130 if not value:
131 return False
132 try:
133 if cidr:
134 if strict and value.count("/") != 1:
135 raise ValueError("IPv6 address was expected in CIDR notation")
136 return IPv6Network(value, strict=not host_bit)
137 return IPv6Address(value)
138 except (ValueError, AddressValueError, NetmaskValueError):
139 return False