diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 20190095517..c0b0cb2788c 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -163,6 +163,7 @@ def _split_optional_netmask(address): raise AddressValueError("Only one '/' permitted in %r" % address) return addr + def _find_address_range(addresses): """Find a sequence of IPv#Address. @@ -408,6 +409,8 @@ def get_mixed_type_key(obj): class _TotalOrderingMixin: # Helper that derives the other comparison operations from # __lt__ and __eq__ + # We avoid functools.total_ordering because it doesn't handle + # NotImplemented correctly yet (http://bugs.python.org/issue10042) def __eq__(self, other): raise NotImplementedError def __ne__(self, other): @@ -455,6 +458,22 @@ class _IPAddressBase(_TotalOrderingMixin): msg = '%200s has no version specified' % (type(self),) raise NotImplementedError(msg) + def _check_int_address(self, address): + if address < 0: + msg = "%d (< 0) is not permitted as an IPv%d address" + raise AddressValueError(msg % (address, self._version)) + if address > self._ALL_ONES: + msg = "%d (>= 2**%d) is not permitted as an IPv%d address" + raise AddressValueError(msg % (address, self._max_prefixlen, + self._version)) + + def _check_packed_address(self, address, expected_len): + address_len = len(address) + if address_len != expected_len: + msg = "%r (len %d != %d) is not permitted as an IPv%d address" + raise AddressValueError(msg % (address, address_len, + expected_len, self._version)) + def _ip_int_from_prefix(self, prefixlen=None): """Turn the prefix length netmask into a int for comparison. @@ -1215,16 +1234,13 @@ class IPv4Address(_BaseV4, _BaseAddress): # Efficient constructor from integer. if isinstance(address, int): + self._check_int_address(address) self._ip = address - if address < 0 or address > self._ALL_ONES: - raise AddressValueError(address) return # Constructing from a packed address if isinstance(address, bytes): - if len(address) != 4: - msg = "Packed address %r must be exactly 4 bytes" - raise AddressValueError(msg % address) + self._check_packed_address(address, 4) self._ip = struct.unpack('!I', address)[0] return @@ -1368,11 +1384,7 @@ class IPv4Network(_BaseV4, _BaseNetwork): # Constructing from a packed address if isinstance(address, bytes): - if len(address) != 4: - msg = "Packed address %r must be exactly 4 bytes" - raise AddressValueError(msg % address) - self.network_address = IPv4Address( - struct.unpack('!I', address)[0]) + self.network_address = IPv4Address(address) self._prefixlen = self._max_prefixlen self.netmask = IPv4Address(self._ALL_ONES) #fixme: address/network test here @@ -1380,11 +1392,9 @@ class IPv4Network(_BaseV4, _BaseNetwork): # Efficient constructor from integer. if isinstance(address, int): + self.network_address = IPv4Address(address) self._prefixlen = self._max_prefixlen self.netmask = IPv4Address(self._ALL_ONES) - if address < 0 or address > self._ALL_ONES: - raise AddressValueError(address) - self.network_address = IPv4Address(address) #fixme: address/network test here. return @@ -1868,16 +1878,13 @@ class IPv6Address(_BaseV6, _BaseAddress): # Efficient constructor from integer. if isinstance(address, int): + self._check_int_address(address) self._ip = address - if address < 0 or address > self._ALL_ONES: - raise AddressValueError(address) return # Constructing from a packed address if isinstance(address, bytes): - if len(address) != 16: - msg = "Packed address %r must be exactly 16 bytes" - raise AddressValueError(msg % address) + self._check_packed_address(address, 16) tmp = struct.unpack('!QQ', address) self._ip = (tmp[0] << 64) | tmp[1] return @@ -2014,8 +2021,6 @@ class IPv6Network(_BaseV6, _BaseNetwork): # Efficient constructor from integer. if isinstance(address, int): - if address < 0 or address > self._ALL_ONES: - raise AddressValueError(address) self.network_address = IPv6Address(address) self._prefixlen = self._max_prefixlen self.netmask = IPv6Address(self._ALL_ONES) @@ -2023,11 +2028,7 @@ class IPv6Network(_BaseV6, _BaseNetwork): # Constructing from a packed address if isinstance(address, bytes): - if len(address) != 16: - msg = "Packed address %r must be exactly 16 bytes" - raise AddressValueError(msg % address) - tmp = struct.unpack('!QQ', address) - self.network_address = IPv6Address((tmp[0] << 64) | tmp[1]) + self.network_address = IPv6Address(address) self._prefixlen = self._max_prefixlen self.netmask = IPv6Address(self._ALL_ONES) return diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 5aaf7367401..8dc4767a016 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -23,6 +23,10 @@ class ErrorReporting(unittest.TestCase): # address parts) that don't have an obvious home in the main test # suite + @property + def factory(self): + raise NotImplementedError + @contextlib.contextmanager def assertCleanError(self, exc_type, details, *args): """ @@ -49,11 +53,64 @@ class ErrorReporting(unittest.TestCase): return self.assertCleanError(ipaddress.NetmaskValueError, details, *args) -class AddressErrors_v4(ErrorReporting): +class CommonErrorsMixin: def test_empty_address(self): with self.assertAddressError("Address cannot be empty"): - ipaddress.IPv4Address("") + self.factory("") + + def test_floats_rejected(self): + with self.assertAddressError(re.escape(repr("1.0"))): + self.factory(1.0) + +class CommonErrorsMixin_v4(CommonErrorsMixin): + + def test_negative_ints_rejected(self): + msg = "-1 (< 0) is not permitted as an IPv4 address" + with self.assertAddressError(re.escape(msg)): + self.factory(-1) + + def test_large_ints_rejected(self): + msg = "%d (>= 2**32) is not permitted as an IPv4 address" + with self.assertAddressError(re.escape(msg % 2**32)): + self.factory(2**32) + + def test_bad_packed_length(self): + def assertBadLength(length): + addr = b'\x00' * length + msg = "%r (len %d != 4) is not permitted as an IPv4 address" + with self.assertAddressError(re.escape(msg % (addr, length))): + self.factory(addr) + + assertBadLength(3) + assertBadLength(5) + +class CommonErrorsMixin_v6(CommonErrorsMixin): + + def test_negative_ints_rejected(self): + msg = "-1 (< 0) is not permitted as an IPv6 address" + with self.assertAddressError(re.escape(msg)): + self.factory(-1) + + def test_large_ints_rejected(self): + msg = "%d (>= 2**128) is not permitted as an IPv6 address" + with self.assertAddressError(re.escape(msg % 2**128)): + self.factory(2**128) + + def test_bad_packed_length(self): + def assertBadLength(length): + addr = b'\x00' * length + msg = "%r (len %d != 16) is not permitted as an IPv6 address" + with self.assertAddressError(re.escape(msg % (addr, length))): + self.factory(addr) + self.factory(addr) + + assertBadLength(15) + assertBadLength(17) + + +class AddressErrors_v4(ErrorReporting, CommonErrorsMixin_v4): + factory = ipaddress.IPv4Address def test_network_passed_as_address(self): addr = "127.0.0.1/24" @@ -133,22 +190,9 @@ class AddressErrors_v4(ErrorReporting): assertBadOctet("12345.67899.-54321.-98765", 12345) assertBadOctet("257.0.0.0", 257) - def test_bad_packed_length(self): - def assertBadLength(length): - addr = b'\x00' * length - msg = "Packed address %r must be exactly 4 bytes" % addr - with self.assertAddressError(re.escape(msg)): - ipaddress.IPv4Address(addr) - assertBadLength(3) - assertBadLength(5) - - -class AddressErrors_v6(ErrorReporting): - - def test_empty_address(self): - with self.assertAddressError("Address cannot be empty"): - ipaddress.IPv6Address("") +class AddressErrors_v6(ErrorReporting, CommonErrorsMixin_v6): + factory = ipaddress.IPv6Address def test_network_passed_as_address(self): addr = "::1/24" @@ -277,24 +321,10 @@ class AddressErrors_v6(ErrorReporting): assertBadPart("02001:db8::", "02001") assertBadPart('2001:888888::1', "888888") - def test_bad_packed_length(self): - def assertBadLength(length): - addr = b'\x00' * length - msg = "Packed address %r must be exactly 16 bytes" % addr - with self.assertAddressError(re.escape(msg)): - ipaddress.IPv6Address(addr) - assertBadLength(15) - assertBadLength(17) - - -class NetmaskErrorsMixin_v4: +class NetmaskErrorsMixin_v4(CommonErrorsMixin_v4): """Input validation on interfaces and networks is very similar""" - @property - def factory(self): - raise NotImplementedError - def test_split_netmask(self): addr = "1.2.3.4/32/24" with self.assertAddressError("Only one '/' permitted in %r" % addr): @@ -305,7 +335,8 @@ class NetmaskErrorsMixin_v4: with self.assertAddressError(details): self.factory(addr) - assertBadAddress("", "Address cannot be empty") + assertBadAddress("/", "Address cannot be empty") + assertBadAddress("/8", "Address cannot be empty") assertBadAddress("bogus", "Expected 4 octets") assertBadAddress("google.com", "Expected 4 octets") assertBadAddress("10/8", "Expected 4 octets") @@ -325,17 +356,6 @@ class NetmaskErrorsMixin_v4: assertBadNetmask("1.1.1.1", "1.a.2.3") assertBadNetmask("1.1.1.1", "pudding") - def test_bad_packed_length(self): - def assertBadLength(length): - addr = b'\x00' * length - msg = "Packed address %r must be exactly 4 bytes" % addr - with self.assertAddressError(re.escape(msg)): - self.factory(addr) - - assertBadLength(3) - assertBadLength(5) - - class InterfaceErrors_v4(ErrorReporting, NetmaskErrorsMixin_v4): factory = ipaddress.IPv4Interface @@ -343,13 +363,9 @@ class NetworkErrors_v4(ErrorReporting, NetmaskErrorsMixin_v4): factory = ipaddress.IPv4Network -class NetmaskErrorsMixin_v6: +class NetmaskErrorsMixin_v6(CommonErrorsMixin_v6): """Input validation on interfaces and networks is very similar""" - @property - def factory(self): - raise NotImplementedError - def test_split_netmask(self): addr = "cafe:cafe::/128/190" with self.assertAddressError("Only one '/' permitted in %r" % addr): @@ -360,7 +376,8 @@ class NetmaskErrorsMixin_v6: with self.assertAddressError(details): self.factory(addr) - assertBadAddress("", "Address cannot be empty") + assertBadAddress("/", "Address cannot be empty") + assertBadAddress("/8", "Address cannot be empty") assertBadAddress("google.com", "At least 3 parts") assertBadAddress("1.2.3.4", "At least 3 parts") assertBadAddress("10/8", "At least 3 parts") @@ -378,17 +395,6 @@ class NetmaskErrorsMixin_v6: assertBadNetmask("::1", "129") assertBadNetmask("::1", "pudding") - def test_bad_packed_length(self): - def assertBadLength(length): - addr = b'\x00' * length - msg = "Packed address %r must be exactly 16 bytes" % addr - with self.assertAddressError(re.escape(msg)): - self.factory(addr) - - assertBadLength(15) - assertBadLength(17) - - class InterfaceErrors_v6(ErrorReporting, NetmaskErrorsMixin_v6): factory = ipaddress.IPv6Interface @@ -585,10 +591,6 @@ class IpaddrUnitTest(unittest.TestCase): def testIpFromInt(self): self.assertEqual(self.ipv4_interface._ip, ipaddress.IPv4Interface(16909060)._ip) - self.assertRaises(ipaddress.AddressValueError, - ipaddress.IPv4Interface, 2**32) - self.assertRaises(ipaddress.AddressValueError, - ipaddress.IPv4Interface, -1) ipv4 = ipaddress.ip_network('1.2.3.4') ipv6 = ipaddress.ip_network('2001:658:22a:cafe:200:0:0:1') @@ -598,14 +600,6 @@ class IpaddrUnitTest(unittest.TestCase): v6_int = 42540616829182469433547762482097946625 self.assertEqual(self.ipv6_interface._ip, ipaddress.IPv6Interface(v6_int)._ip) - self.assertRaises(ipaddress.AddressValueError, - ipaddress.IPv6Interface, 2**128) - self.assertRaises(ipaddress.AddressValueError, - ipaddress.IPv6Interface, -1) - self.assertRaises(ipaddress.AddressValueError, - ipaddress.IPv6Network, 2**128) - self.assertRaises(ipaddress.AddressValueError, - ipaddress.IPv6Network, -1) self.assertEqual(ipaddress.ip_network(self.ipv4_address._ip).version, 4)