Update ipaddress from CPython 3.10.6

This commit is contained in:
CPython Developers authored and Jeong YunWon committed 2022-08-15 04:24:58 +09:00
1 parent 2410c56e2e
commit 44a7ec2c0d
2 files changed
+696 -46

No files matched your search

+130 -18
View File
@@ -16,6 +16,7 @@ import functools
IPV4LENGTH = 32
IPV6LENGTH = 128
class AddressValueError(ValueError):
"""A Value Error related to the address."""
@@ -50,8 +51,7 @@ def ip_address(address):
except (AddressValueError, NetmaskValueError):
pass
raise ValueError('%r does not appear to be an IPv4 or IPv6 address' %
address)
raise ValueError(f'{address!r} does not appear to be an IPv4 or IPv6 address')
def ip_network(address, strict=True):
@@ -80,8 +80,7 @@ def ip_network(address, strict=True):
except (AddressValueError, NetmaskValueError):
pass
raise ValueError('%r does not appear to be an IPv4 or IPv6 network' %
address)
raise ValueError(f'{address!r} does not appear to be an IPv4 or IPv6 network')
def ip_interface(address):
@@ -115,8 +114,7 @@ def ip_interface(address):
except (AddressValueError, NetmaskValueError):
pass
raise ValueError('%r does not appear to be an IPv4 or IPv6 interface' %
address)
raise ValueError(f'{address!r} does not appear to be an IPv4 or IPv6 interface')
def v4_int_to_packed(address):
@@ -159,7 +157,7 @@ def _split_optional_netmask(address):
"""Helper to split the netmask and raise AddressValueError if needed"""
addr = str(address).split('/')
if len(addr) > 2:
raise AddressValueError("Only one '/' permitted in %r" % address)
raise AddressValueError(f"Only one '/' permitted in {address!r}")
return addr
@@ -560,6 +558,8 @@ class _IPAddressBase:
return self.__class__, (str(self),)
_address_fmt_re = None
@functools.total_ordering
class _BaseAddress(_IPAddressBase):
@@ -618,6 +618,55 @@ class _BaseAddress(_IPAddressBase):
def __reduce__(self):
return self.__class__, (self._ip,)
def __format__(self, fmt):
"""Returns an IP address as a formatted string.
Supported presentation types are:
's': returns the IP address as a string (default)
'b': converts to binary and returns a zero-padded string
'X' or 'x': converts to upper- or lower-case hex and returns a zero-padded string
'n': the same as 'b' for IPv4 and 'x' for IPv6
For binary and hex presentation types, the alternate form specifier
'#' and the grouping option '_' are supported.
"""
# Support string formatting
if not fmt or fmt[-1] == 's':
return format(str(self), fmt)
# From here on down, support for 'bnXx'
global _address_fmt_re
if _address_fmt_re is None:
import re
_address_fmt_re = re.compile('(#?)(_?)([xbnX])')
m = _address_fmt_re.fullmatch(fmt)
if not m:
return super().__format__(fmt)
alternate, grouping, fmt_base = m.groups()
# Set some defaults
if fmt_base == 'n':
if self._version == 4:
fmt_base = 'b' # Binary is default for ipv4
else:
fmt_base = 'x' # Hex is default for ipv6
if fmt_base == 'b':
padlen = self._max_prefixlen
else:
padlen = self._max_prefixlen // 4
if grouping:
padlen += padlen // 4 - 1
if alternate:
padlen += 2 # 0b or 0x
return format(int(self), f'{alternate}0{padlen}{grouping}{fmt_base}')
@functools.total_ordering
class _BaseNetwork(_IPAddressBase):
@@ -1073,7 +1122,6 @@ class _BaseNetwork(_IPAddressBase):
return (self.network_address.is_loopback and
self.broadcast_address.is_loopback)
class _BaseV4:
"""Base IPv4 object.
@@ -1164,7 +1212,7 @@ class _BaseV4:
"""
if not octet_str:
raise ValueError("Empty octet not permitted")
# Whitelist the characters, since int() allows a lot of bizarre stuff.
# Reject non-ASCII digits.
if not (octet_str.isascii() and octet_str.isdigit()):
msg = "Only decimal digits permitted in %r"
raise ValueError(msg % octet_str)
@@ -1173,6 +1221,11 @@ class _BaseV4:
if len(octet_str) > 3:
msg = "At most 3 characters permitted in %r"
raise ValueError(msg % octet_str)
# Handle leading zeros as strict as glibc's inet_pton()
# See security bug bpo-36384
if octet_str != '0' and octet_str[0] == '0':
msg = "Leading zeros are not permitted in %r"
raise ValueError(msg % octet_str)
# Convert to integer (we know digits are legal)
octet_int = int(octet_str, 10)
if octet_int > 255:
@@ -1248,7 +1301,7 @@ class IPv4Address(_BaseV4, _BaseAddress):
# which converts into a formatted IP string.
addr_str = str(address)
if '/' in addr_str:
raise AddressValueError("Unexpected '/' in %r" % address)
raise AddressValueError(f"Unexpected '/' in {address!r}")
self._ip = self._ip_int_from_string(addr_str)
@property
@@ -1347,7 +1400,7 @@ class IPv4Interface(IPv4Address):
def __eq__(self, other):
address_equal = IPv4Address.__eq__(self, other)
if not address_equal or address_equal is NotImplemented:
if address_equal is NotImplemented or not address_equal:
return address_equal
try:
return self.network == other.network
@@ -1458,6 +1511,8 @@ class IPv4Network(_BaseV4, _BaseNetwork):
if self._prefixlen == (self._max_prefixlen - 1):
self.hosts = self.__iter__
elif self._prefixlen == (self._max_prefixlen):
self.hosts = lambda: [IPv4Address(addr)]
@property
@functools.lru_cache()
@@ -1667,7 +1722,7 @@ class _BaseV6:
[0..FFFF].
"""
# Whitelist the characters, since int() allows a lot of bizarre stuff.
# Reject non-ASCII digits.
if not cls._HEX_DIGITS.issuperset(hextet_str):
raise ValueError("Only hex digits permitted in %r" % hextet_str)
# We do the length check second, since the invalid character error
@@ -1785,6 +1840,26 @@ class _BaseV6:
reverse_chars = self.exploded[::-1].replace(':', '')
return '.'.join(reverse_chars) + '.ip6.arpa'
@staticmethod
def _split_scope_id(ip_str):
"""Helper function to parse IPv6 string address with scope id.
See RFC 4007 for details.
Args:
ip_str: A string, the IPv6 address.
Returns:
(addr, scope_id) tuple.
"""
addr, sep, scope_id = ip_str.partition('%')
if not sep:
scope_id = None
elif not scope_id or '%' in scope_id:
raise AddressValueError('Invalid IPv6 address: "%r"' % ip_str)
return addr, scope_id
@property
def max_prefixlen(self):
return self._max_prefixlen
@@ -1798,7 +1873,7 @@ class IPv6Address(_BaseV6, _BaseAddress):
"""Represent and manipulate single IPv6 Addresses."""
__slots__ = ('_ip', '__weakref__')
__slots__ = ('_ip', '_scope_id', '__weakref__')
def __init__(self, address):
"""Instantiate a new IPv6 address object.
@@ -1821,21 +1896,52 @@ class IPv6Address(_BaseV6, _BaseAddress):
if isinstance(address, int):
self._check_int_address(address)
self._ip = address
self._scope_id = None
return
# Constructing from a packed address
if isinstance(address, bytes):
self._check_packed_address(address, 16)
self._ip = int.from_bytes(address, 'big')
self._scope_id = None
return
# Assume input argument to be string or any object representation
# which converts into a formatted IP string.
addr_str = str(address)
if '/' in addr_str:
raise AddressValueError("Unexpected '/' in %r" % address)
raise AddressValueError(f"Unexpected '/' in {address!r}")
addr_str, self._scope_id = self._split_scope_id(addr_str)
self._ip = self._ip_int_from_string(addr_str)
def __str__(self):
ip_str = super().__str__()
return ip_str + '%' + self._scope_id if self._scope_id else ip_str
def __hash__(self):
return hash((self._ip, self._scope_id))
def __eq__(self, other):
address_equal = super().__eq__(other)
if address_equal is NotImplemented:
return NotImplemented
if not address_equal:
return False
return self._scope_id == getattr(other, '_scope_id', None)
@property
def scope_id(self):
"""Identifier of a particular zone of the address's scope.
See RFC 4007 for details.
Returns:
A string identifying the zone of the address if specified, else None.
"""
return self._scope_id
@property
def packed(self):
"""The binary representation of this address."""
@@ -1894,9 +2000,13 @@ class IPv6Address(_BaseV6, _BaseAddress):
Returns:
A boolean, True if the address is reserved per
iana-ipv6-special-registry.
iana-ipv6-special-registry, or is ipv4_mapped and is
reserved in the iana-ipv4-special-registry.
"""
ipv4_mapped = self.ipv4_mapped
if ipv4_mapped is not None:
return ipv4_mapped.is_private
return any(self in net for net in self._constants._private_networks)
@property
@@ -1989,12 +2099,12 @@ class IPv6Interface(IPv6Address):
return self.network.hostmask
def __str__(self):
return '%s/%d' % (self._string_from_ip_int(self._ip),
return '%s/%d' % (super().__str__(),
self._prefixlen)
def __eq__(self, other):
address_equal = IPv6Address.__eq__(self, other)
if not address_equal or address_equal is NotImplemented:
if address_equal is NotImplemented or not address_equal:
return address_equal
try:
return self.network == other.network
@@ -2007,7 +2117,7 @@ class IPv6Interface(IPv6Address):
def __lt__(self, other):
address_less = IPv6Address.__lt__(self, other)
if address_less is NotImplemented:
return NotImplemented
return address_less
try:
return (self.network < other.network or
self.network == other.network and address_less)
@@ -2110,6 +2220,8 @@ class IPv6Network(_BaseV6, _BaseNetwork):
if self._prefixlen == (self._max_prefixlen - 1):
self.hosts = self.__iter__
elif self._prefixlen == self._max_prefixlen:
self.hosts = lambda: [IPv6Address(addr)]
def hosts(self):
"""Generate Iterator over usable hosts in a network.
+566 -28
View File
File diff suppressed because it is too large. Load diff