Skip to content

Commit 205afe6

Browse files
committed
bpo-27860: clean up ipaddress
1 parent 2430d53 commit 205afe6

File tree

2 files changed

+138
-113
lines changed

2 files changed

+138
-113
lines changed

Lib/ipaddress.py

Lines changed: 110 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -499,38 +499,30 @@ def _prefix_from_prefix_string(cls, prefixlen_str):
499499
return prefixlen
500500

501501
@classmethod
502-
def _prefix_from_ip_string(cls, ip_str):
503-
"""Turn a netmask/hostmask string into a prefix length
502+
def _split_addr_prefix(cls, arg):
503+
"""Make a (address, prefix) tuple from the given argument.
504504
505505
Args:
506-
ip_str: The netmask/hostmask to be converted
506+
arg: A string, bytes object or a tuple representing an address and
507+
a network prefix.
507508
508509
Returns:
509-
An integer, the prefix length.
510-
511-
Raises:
512-
NetmaskValueError: If the input is not a valid netmask/hostmask
510+
A (address, netmask/prefix) tuple.
513511
"""
514-
# Parse the netmask/hostmask like an IP address.
515-
try:
516-
ip_int = cls._ip_int_from_string(ip_str)
517-
except AddressValueError:
518-
cls._report_invalid_netmask(ip_str)
519-
520-
# Try matching a netmask (this would be /1*0*/ as a bitwise regexp).
521-
# Note that the two ambiguous cases (all-ones and all-zeroes) are
522-
# treated as netmasks.
523-
try:
524-
return cls._prefix_from_ip_int(ip_int)
525-
except ValueError:
526-
pass
512+
if isinstance(arg, (int, bytes)):
513+
addr = arg
514+
prefix = cls._max_prefixlen
515+
elif isinstance(arg, tuple) and len(arg) == 2:
516+
addr, prefix = arg
517+
else:
518+
arg = _split_optional_netmask(arg)
519+
addr = arg[0]
520+
if len(arg) == 2:
521+
prefix = arg[1]
522+
else:
523+
prefix = cls._max_prefixlen
527524

528-
# Invert the bits, and try matching a /0+1+/ hostmask instead.
529-
ip_int ^= cls._ALL_ONES
530-
try:
531-
return cls._prefix_from_ip_int(ip_int)
532-
except ValueError:
533-
cls._report_invalid_netmask(ip_str)
525+
return addr, prefix
534526

535527
def __reduce__(self):
536528
return self.__class__, (str(self),)
@@ -1080,20 +1072,41 @@ def _make_netmask(cls, arg):
10801072
Argument can be:
10811073
- an integer (the prefix length)
10821074
- a string representing the prefix length (e.g. "24")
1083-
- a string representing the prefix netmask (e.g. "255.255.255.0")
1075+
- a string representing the netmask or hostmask (e.g. "255.255.255.0")
1076+
- a bytes object representing a netmask or hostmask
1077+
(e.g. b"\xff\xff\xff\x00")
1078+
- an IPv4Address object representing the netmask or hostmask
10841079
"""
10851080
if arg not in cls._netmask_cache:
10861081
if isinstance(arg, int):
10871082
prefixlen = arg
1083+
netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen))
10881084
else:
10891085
try:
1086+
if isinstance(arg, IPv4Address):
1087+
netmask = arg
1088+
else:
1089+
netmask = IPv4Address(arg)
1090+
except AddressValueError:
10901091
# Check for a netmask in prefix length form
10911092
prefixlen = cls._prefix_from_prefix_string(arg)
1092-
except NetmaskValueError:
1093-
# Check for a netmask or hostmask in dotted-quad form.
1094-
# This may raise NetmaskValueError.
1095-
prefixlen = cls._prefix_from_ip_string(arg)
1096-
netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen))
1093+
netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen))
1094+
else:
1095+
try:
1096+
# Try matching a netmask (this would be /1*0*/ as a
1097+
# bitwise regexp). Note that the two ambiguous cases
1098+
# (all-ones and all-zeroes) are treated as netmasks.
1099+
prefixlen = cls._prefix_from_ip_int(netmask._ip)
1100+
except ValueError:
1101+
# Invert the bits, and try matching a /0+1+/ hostmask
1102+
# instead.
1103+
ip_int = netmask._ip ^ cls._ALL_ONES
1104+
try:
1105+
prefixlen = cls._prefix_from_ip_int(ip_int)
1106+
except ValueError:
1107+
cls._report_invalid_netmask(arg)
1108+
else:
1109+
netmask = IPv4Address(ip_int)
10971110
cls._netmask_cache[arg] = netmask, prefixlen
10981111
return cls._netmask_cache[arg]
10991112

@@ -1167,6 +1180,24 @@ def _string_from_ip_int(cls, ip_int):
11671180
"""
11681181
return '.'.join(map(str, ip_int.to_bytes(4, 'big')))
11691182

1183+
@classmethod
1184+
def _get_addr_prefix_tuple(cls, arg):
1185+
"""Make a (address, (netmask, prefix)) tuple from the given argument.
1186+
1187+
Args:
1188+
arg: A string, bytes object or a tuple representing an address and
1189+
a network prefix.
1190+
1191+
Returns:
1192+
A (address, (netmask, prefix)) tuple.
1193+
"""
1194+
addr, prefix = cls._split_addr_prefix(arg)
1195+
if isinstance(addr, IPv4Address):
1196+
addr = IPv4Address(addr._ip)
1197+
else:
1198+
addr = IPv4Address(addr)
1199+
return (addr, cls._make_netmask(prefix))
1200+
11701201
def _reverse_pointer(self):
11711202
"""Return the reverse DNS pointer name for the IPv4 address.
11721203
@@ -1305,30 +1336,11 @@ def is_link_local(self):
13051336
class IPv4Interface(IPv4Address):
13061337

13071338
def __init__(self, address):
1308-
if isinstance(address, (bytes, int)):
1309-
IPv4Address.__init__(self, address)
1310-
self.network = IPv4Network(self._ip)
1311-
self._prefixlen = self._max_prefixlen
1312-
return
1313-
1314-
if isinstance(address, tuple):
1315-
IPv4Address.__init__(self, address[0])
1316-
if len(address) > 1:
1317-
self._prefixlen = int(address[1])
1318-
else:
1319-
self._prefixlen = self._max_prefixlen
1320-
1321-
self.network = IPv4Network(address, strict=False)
1322-
self.netmask = self.network.netmask
1323-
self.hostmask = self.network.hostmask
1324-
return
1325-
1326-
addr = _split_optional_netmask(address)
1327-
IPv4Address.__init__(self, addr[0])
1328-
1329-
self.network = IPv4Network(address, strict=False)
1330-
self._prefixlen = self.network._prefixlen
1339+
addr_prefix = self._get_addr_prefix_tuple(address)
1340+
addr, (self.netmask, self._prefixlen) = addr_prefix
1341+
super().__init__(addr._ip)
13311342

1343+
self.network = IPv4Network((addr, self._prefixlen), strict=False)
13321344
self.netmask = self.network.netmask
13331345
self.hostmask = self.network.hostmask
13341346

@@ -1435,30 +1447,15 @@ def __init__(self, address, strict=True):
14351447
ValueError: If strict is True and a network address is not
14361448
supplied.
14371449
"""
1438-
# Constructing from a packed address or integer
1439-
if isinstance(address, (int, bytes)):
1440-
addr = address
1441-
mask = self._max_prefixlen
1442-
# Constructing from a tuple (addr, [mask])
1443-
elif isinstance(address, tuple):
1444-
addr = address[0]
1445-
mask = address[1] if len(address) > 1 else self._max_prefixlen
1446-
# Assume input argument to be string or any object representation
1447-
# which converts into a formatted IP prefix string.
1448-
else:
1449-
args = _split_optional_netmask(address)
1450-
addr = self._ip_int_from_string(args[0])
1451-
mask = args[1] if len(args) == 2 else self._max_prefixlen
1450+
addr_prefix = self._get_addr_prefix_tuple(address)
1451+
self.network_address, (self.netmask, self._prefixlen) = addr_prefix
14521452

1453-
self.network_address = IPv4Address(addr)
1454-
self.netmask, self._prefixlen = self._make_netmask(mask)
14551453
packed = int(self.network_address)
14561454
if packed & int(self.netmask) != packed:
14571455
if strict:
14581456
raise ValueError('%s has host bits set' % self)
14591457
else:
1460-
self.network_address = IPv4Address(packed &
1461-
int(self.netmask))
1458+
self.network_address = IPv4Address(packed & int(self.netmask))
14621459

14631460
if self._prefixlen == (self._max_prefixlen - 1):
14641461
self.hosts = self.__iter__
@@ -1539,14 +1536,26 @@ def _make_netmask(cls, arg):
15391536
Argument can be:
15401537
- an integer (the prefix length)
15411538
- a string representing the prefix length (e.g. "24")
1542-
- a string representing the prefix netmask (e.g. "255.255.255.0")
1539+
- a bytes object representing the netmask (e.g. b'\xff'*16)
1540+
- an IPv6Address object representing the netmask
15431541
"""
15441542
if arg not in cls._netmask_cache:
15451543
if isinstance(arg, int):
15461544
prefixlen = arg
1545+
netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen))
15471546
else:
1548-
prefixlen = cls._prefix_from_prefix_string(arg)
1549-
netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen))
1547+
if isinstance(arg, (bytes, IPv6Address)):
1548+
netmask = arg
1549+
if isinstance(netmask, bytes):
1550+
netmask = IPv6Address(netmask)
1551+
try:
1552+
prefixlen = cls._prefix_from_ip_int(netmask._ip)
1553+
except ValueError:
1554+
cls._report_invalid_netmask(arg)
1555+
else:
1556+
# Check for a netmask in prefix length form
1557+
prefixlen = cls._prefix_from_prefix_string(arg)
1558+
netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen))
15501559
cls._netmask_cache[arg] = netmask, prefixlen
15511560
return cls._netmask_cache[arg]
15521561

@@ -1778,6 +1787,24 @@ def _explode_shorthand_ip_string(self):
17781787
return '%s/%d' % (':'.join(parts), self._prefixlen)
17791788
return ':'.join(parts)
17801789

1790+
@classmethod
1791+
def _get_addr_prefix_tuple(cls, arg):
1792+
"""Make a (address, (netmask, prefix)) tuple from the given argument.
1793+
1794+
Args:
1795+
arg: A string, bytes object or a tuple representing an address and
1796+
a network prefix.
1797+
1798+
Returns:
1799+
A (address, (netmask, prefix)) tuple.
1800+
"""
1801+
addr, prefix = cls._split_addr_prefix(arg)
1802+
if isinstance(addr, IPv6Address):
1803+
addr = IPv6Address(addr._ip)
1804+
else:
1805+
addr = IPv6Address(addr)
1806+
return (addr, cls._make_netmask(prefix))
1807+
17811808
def _reverse_pointer(self):
17821809
"""Return the reverse DNS pointer name for the IPv6 address.
17831810
@@ -1979,27 +2006,12 @@ def sixtofour(self):
19792006
class IPv6Interface(IPv6Address):
19802007

19812008
def __init__(self, address):
1982-
if isinstance(address, (bytes, int)):
1983-
IPv6Address.__init__(self, address)
1984-
self.network = IPv6Network(self._ip)
1985-
self._prefixlen = self._max_prefixlen
1986-
return
1987-
if isinstance(address, tuple):
1988-
IPv6Address.__init__(self, address[0])
1989-
if len(address) > 1:
1990-
self._prefixlen = int(address[1])
1991-
else:
1992-
self._prefixlen = self._max_prefixlen
1993-
self.network = IPv6Network(address, strict=False)
1994-
self.netmask = self.network.netmask
1995-
self.hostmask = self.network.hostmask
1996-
return
2009+
addr_prefix = self._get_addr_prefix_tuple(address)
2010+
addr, (self.netmask, self._prefixlen) = addr_prefix
2011+
super().__init__(addr._ip)
19972012

1998-
addr = _split_optional_netmask(address)
1999-
IPv6Address.__init__(self, addr[0])
2000-
self.network = IPv6Network(address, strict=False)
2013+
self.network = IPv6Network((addr, self._prefixlen), strict=False)
20012014
self.netmask = self.network.netmask
2002-
self._prefixlen = self.network._prefixlen
20032015
self.hostmask = self.network.hostmask
20042016

20052017
def __str__(self):
@@ -2110,30 +2122,15 @@ def __init__(self, address, strict=True):
21102122
ValueError: If strict was True and a network address was not
21112123
supplied.
21122124
"""
2113-
# Constructing from a packed address or integer
2114-
if isinstance(address, (int, bytes)):
2115-
addr = address
2116-
mask = self._max_prefixlen
2117-
# Constructing from a tuple (addr, [mask])
2118-
elif isinstance(address, tuple):
2119-
addr = address[0]
2120-
mask = address[1] if len(address) > 1 else self._max_prefixlen
2121-
# Assume input argument to be string or any object representation
2122-
# which converts into a formatted IP prefix string.
2123-
else:
2124-
args = _split_optional_netmask(address)
2125-
addr = self._ip_int_from_string(args[0])
2126-
mask = args[1] if len(args) == 2 else self._max_prefixlen
2125+
addr_prefix = self._get_addr_prefix_tuple(address)
2126+
self.network_address, (self.netmask, self._prefixlen) = addr_prefix
21272127

2128-
self.network_address = IPv6Address(addr)
2129-
self.netmask, self._prefixlen = self._make_netmask(mask)
21302128
packed = int(self.network_address)
21312129
if packed & int(self.netmask) != packed:
21322130
if strict:
21332131
raise ValueError('%s has host bits set' % self)
21342132
else:
2135-
self.network_address = IPv6Address(packed &
2136-
int(self.netmask))
2133+
self.network_address = IPv6Address(packed & int(self.netmask))
21372134

21382135
if self._prefixlen == (self._max_prefixlen - 1):
21392136
self.hosts = self.__iter__

Lib/test/test_ipaddress.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,22 @@ def test_valid_netmask(self):
439439
self.assertEqual(
440440
str(self.factory('0.0.0.0/%s' % net.hostmask)), net_str)
441441

442+
def test_tuple_constructor(self):
443+
tests = [
444+
('192.168.0.0/24', (('192.168.0.0', b'\xff\xff\xff\x00'))),
445+
('192.168.0.0/23', ((bytes([192, 168, 0, 0]), b'\xff\xff\xfe\x00'))),
446+
('10.0.0.0/8',
447+
((ipaddress.IPv4Address('10.0.0.0'), b'\x00\xff\xff\xff'))),
448+
('10.0.0.0/8',
449+
((ipaddress.IPv4Interface('10.0.0.0/32'), b'\x00\xff\xff\xff'))),
450+
('172.16.0.0/16',
451+
(('172.16.0.0', ipaddress.IPv4Address('255.255.0.0')))),
452+
('172.16.0.0/16',
453+
(('172.16.0.0', ipaddress.IPv4Address('0.0.255.255')))),
454+
]
455+
for expected, arg in tests:
456+
self.assertInstancesEqual(arg, expected)
457+
442458
def test_netmask_errors(self):
443459
def assertBadNetmask(addr, netmask):
444460
msg = "%r is not a valid netmask" % netmask
@@ -555,6 +571,18 @@ def test_valid_netmask(self):
555571
# Zero prefix is treated as decimal.
556572
self.assertEqual(str(self.factory('::/0%d' % i)), net_str)
557573

574+
def test_tuple_constructor(self):
575+
tests = [
576+
('fc00::/8', (('fc00::', b'\xff' + 15 * b'\x00'))),
577+
('fc12:3456::/39',
578+
((b'\xfc\x12\x34\x56' + 12 * b'\x00',
579+
4 * b'\xff' + b'\xfe' + 11 * b'\x00'))),
580+
('2001:db8::/32',
581+
(('2001:db8::', ipaddress.IPv6Address('ffff:ffff::')))),
582+
]
583+
for expected, arg in tests:
584+
self.assertInstancesEqual(arg, expected)
585+
558586
def test_netmask_errors(self):
559587
def assertBadNetmask(addr, netmask):
560588
msg = "%r is not a valid netmask" % netmask

0 commit comments

Comments
 (0)