Issue 14814: Provide more informative error messages in ipaddress, and ensure that errors are caught as expected

This commit is contained in:
Nick Coghlan 2012-07-07 19:23:53 +10:00
parent 912238e3ac
commit 36f8dcde06
3 changed files with 502 additions and 196 deletions

View file

@ -17,7 +17,6 @@ import struct
IPV4LENGTH = 32
IPV6LENGTH = 128
class AddressValueError(ValueError):
"""A Value Error related to the address."""
@ -117,7 +116,7 @@ def ip_interface(address):
except (AddressValueError, NetmaskValueError):
pass
raise ValueError('%r does not appear to be an IPv4 or IPv6 network' %
raise ValueError('%r does not appear to be an IPv4 or IPv6 interface' %
address)
@ -157,6 +156,13 @@ def v6_int_to_packed(address):
raise ValueError("Address negative or too large for IPv6")
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)
return addr
def _find_address_range(addresses):
"""Find a sequence of IPv#Address.
@ -481,7 +487,7 @@ class _BaseAddress(_IPAddressBase):
def __init__(self, address):
if (not isinstance(address, bytes)
and '/' in str(address)):
raise AddressValueError(address)
raise AddressValueError("Unexpected '/' in %r" % address)
def __index__(self):
return self._ip
@ -1014,16 +1020,19 @@ class _BaseV4:
AddressValueError: if ip_str isn't a valid IPv4 Address.
"""
if not ip_str:
raise AddressValueError('Address cannot be empty')
octets = ip_str.split('.')
if len(octets) != 4:
raise AddressValueError(ip_str)
raise AddressValueError("Expected 4 octets in %r" % ip_str)
packed_ip = 0
for oc in octets:
try:
packed_ip = (packed_ip << 8) | self._parse_octet(oc)
except ValueError:
raise AddressValueError(ip_str) from None
except ValueError as exc:
raise AddressValueError("%s in %r" % (exc, ip_str)) from None
return packed_ip
def _parse_octet(self, octet_str):
@ -1039,15 +1048,21 @@ class _BaseV4:
ValueError: if the octet isn't strictly a decimal from [0..255].
"""
if not octet_str:
raise ValueError("Empty octet not permitted")
# Whitelist the characters, since int() allows a lot of bizarre stuff.
# Higher level wrappers convert these to more informative errors
if not self._DECIMAL_DIGITS.issuperset(octet_str):
raise ValueError
raise ValueError("Only decimal digits permitted in %r" % octet_str)
# Convert to integer (we know digits are legal)
octet_int = int(octet_str, 10)
# Disallow leading zeroes, because no clear standard exists on
# whether these should be interpreted as decimal or octal.
if octet_int > 255 or (octet_str[0] == '0' and len(octet_str) > 1):
raise ValueError
# Any octets that look like they *might* be written in octal,
# and which don't look exactly the same in both octal and
# decimal are rejected as ambiguous
if octet_int > 7 and octet_str[0] == '0':
raise ValueError("Ambiguous leading zero in %r not permitted" %
octet_str)
if octet_int > 255:
raise ValueError("Octet %d > 255 not permitted" % octet_int)
return octet_int
def _string_from_ip_int(self, ip_int):
@ -1080,7 +1095,13 @@ class _BaseV4:
"""
mask = netmask.split('.')
if len(mask) == 4:
if [x for x in mask if int(x) not in self._valid_mask_octets]:
for x in mask:
try:
if int(x) in self._valid_mask_octets:
continue
except ValueError:
pass
# Found something that isn't an integer or isn't valid
return False
if [y for idx, y in enumerate(mask) if idx > 0 and
y > mask[idx - 1]]:
@ -1251,7 +1272,8 @@ class IPv4Address(_BaseV4, _BaseAddress):
# Constructing from a packed address
if isinstance(address, bytes):
if len(address) != 4:
raise AddressValueError(address)
msg = "Packed address %r must be exactly 4 bytes"
raise AddressValueError(msg % address)
self._ip = struct.unpack('!I', address)[0]
return
@ -1275,9 +1297,7 @@ class IPv4Interface(IPv4Address):
self._prefixlen = self._max_prefixlen
return
addr = str(address).split('/')
if len(addr) > 2:
raise AddressValueError(address)
addr = _split_optional_netmask(address)
IPv4Address.__init__(self, addr[0])
self.network = IPv4Network(address, strict=False)
@ -1382,7 +1402,8 @@ class IPv4Network(_BaseV4, _BaseNetwork):
# Constructing from a packed address
if isinstance(address, bytes):
if len(address) != 4:
raise AddressValueError(address)
msg = "Packed address %r must be exactly 4 bytes"
raise AddressValueError(msg % address)
self.network_address = IPv4Address(
struct.unpack('!I', address)[0])
self._prefixlen = self._max_prefixlen
@ -1402,12 +1423,9 @@ class IPv4Network(_BaseV4, _BaseNetwork):
# Assume input argument to be string or any object representation
# which converts into a formatted IP prefix string.
addr = str(address).split('/')
addr = _split_optional_netmask(address)
self.network_address = IPv4Address(self._ip_int_from_string(addr[0]))
if len(addr) > 2:
raise AddressValueError(address)
if len(addr) == 2:
mask = addr[1].split('.')
@ -1420,14 +1438,15 @@ class IPv4Network(_BaseV4, _BaseNetwork):
self.netmask = IPv4Address(
self._ip_int_from_string(addr[1]) ^ self._ALL_ONES)
else:
raise NetmaskValueError('%s is not a valid netmask'
raise NetmaskValueError('%r is not a valid netmask'
% addr[1])
self._prefixlen = self._prefix_from_ip_int(int(self.netmask))
else:
# We have a netmask in prefix length form.
if not self._is_valid_netmask(addr[1]):
raise NetmaskValueError(addr[1])
raise NetmaskValueError('%r is not a valid netmask'
% addr[1])
self._prefixlen = int(addr[1])
self.netmask = IPv4Address(self._ip_int_from_prefix(
self._prefixlen))
@ -1477,21 +1496,33 @@ class _BaseV6:
AddressValueError: if ip_str isn't a valid IPv6 Address.
"""
if not ip_str:
raise AddressValueError('Address cannot be empty')
parts = ip_str.split(':')
# An IPv6 address needs at least 2 colons (3 parts).
if len(parts) < 3:
raise AddressValueError(ip_str)
_min_parts = 3
if len(parts) < _min_parts:
msg = "At least %d parts expected in %r" % (_min_parts, ip_str)
raise AddressValueError(msg)
# If the address has an IPv4-style suffix, convert it to hexadecimal.
if '.' in parts[-1]:
try:
ipv4_int = IPv4Address(parts.pop())._ip
except AddressValueError as exc:
raise AddressValueError("%s in %r" % (exc, ip_str)) from None
parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF))
parts.append('%x' % (ipv4_int & 0xFFFF))
# An IPv6 address can't have more than 8 colons (9 parts).
if len(parts) > self._HEXTET_COUNT + 1:
raise AddressValueError(ip_str)
# The extra colon comes from using the "::" notation for a single
# leading or trailing zero part.
_max_parts = self._HEXTET_COUNT + 1
if len(parts) > _max_parts:
msg = "At most %d colons permitted in %r" % (_max_parts-1, ip_str)
raise AddressValueError(msg)
# Disregarding the endpoints, find '::' with nothing in between.
# This indicates that a run of zeroes has been skipped.
@ -1501,7 +1532,8 @@ class _BaseV6:
[None])
except ValueError:
# Can't have more than one '::'
raise AddressValueError(ip_str) from None
msg = "At most one '::' permitted in %r" % ip_str
raise AddressValueError(msg) from None
# parts_hi is the number of parts to copy from above/before the '::'
# parts_lo is the number of parts to copy from below/after the '::'
@ -1512,20 +1544,30 @@ class _BaseV6:
if not parts[0]:
parts_hi -= 1
if parts_hi:
raise AddressValueError(ip_str) # ^: requires ^::
msg = "Leading ':' only permitted as part of '::' in %r"
raise AddressValueError(msg % ip_str) # ^: requires ^::
if not parts[-1]:
parts_lo -= 1
if parts_lo:
raise AddressValueError(ip_str) # :$ requires ::$
msg = "Trailing ':' only permitted as part of '::' in %r"
raise AddressValueError(msg % ip_str) # :$ requires ::$
parts_skipped = self._HEXTET_COUNT - (parts_hi + parts_lo)
if parts_skipped < 1:
raise AddressValueError(ip_str)
msg = "Expected at most %d other parts with '::' in %r"
raise AddressValueError(msg % (self._HEXTET_COUNT-1, ip_str))
else:
# Otherwise, allocate the entire address to parts_hi. The
# endpoints could still be empty, but _parse_hextet() will check
# for that.
if len(parts) != self._HEXTET_COUNT:
raise AddressValueError(ip_str)
msg = "Exactly %d parts expected without '::' in %r"
raise AddressValueError(msg % (self._HEXTET_COUNT, ip_str))
if not parts[0]:
msg = "Leading ':' only permitted as part of '::' in %r"
raise AddressValueError(msg % ip_str) # ^: requires ^::
if not parts[-1]:
msg = "Trailing ':' only permitted as part of '::' in %r"
raise AddressValueError(msg % ip_str) # :$ requires ::$
parts_hi = len(parts)
parts_lo = 0
parts_skipped = 0
@ -1541,8 +1583,8 @@ class _BaseV6:
ip_int <<= 16
ip_int |= self._parse_hextet(parts[i])
return ip_int
except ValueError:
raise AddressValueError(ip_str) from None
except ValueError as exc:
raise AddressValueError("%s in %r" % (exc, ip_str)) from None
def _parse_hextet(self, hextet_str):
"""Convert an IPv6 hextet string into an integer.
@ -1561,12 +1603,14 @@ class _BaseV6:
# Whitelist the characters, since int() allows a lot of bizarre stuff.
# Higher level wrappers convert these to more informative errors
if not self._HEX_DIGITS.issuperset(hextet_str):
raise ValueError
raise ValueError("Only hex digits permitted in %r" % hextet_str)
if len(hextet_str) > 4:
raise ValueError
msg = "At most 4 characters permitted in %r"
raise ValueError(msg % hextet_str)
hextet_int = int(hextet_str, 16)
if hextet_int > 0xFFFF:
raise ValueError
# This is unreachable due to the string length check above
raise ValueError("Part %d > 0xFFFF not permitted" % hextet_int)
return hextet_int
def _compress_hextets(self, hextets):
@ -1869,7 +1913,8 @@ class IPv6Address(_BaseV6, _BaseAddress):
# Constructing from a packed address
if isinstance(address, bytes):
if len(address) != 16:
raise AddressValueError(address)
msg = "Packed address %r must be exactly 16 bytes"
raise AddressValueError(msg % address)
tmp = struct.unpack('!QQ', address)
self._ip = (tmp[0] << 64) | tmp[1]
return
@ -1877,9 +1922,6 @@ class IPv6Address(_BaseV6, _BaseAddress):
# Assume input argument to be string or any object representation
# which converts into a formatted IP string.
addr_str = str(address)
if not addr_str:
raise AddressValueError('')
self._ip = self._ip_int_from_string(addr_str)
@property
@ -1897,7 +1939,7 @@ class IPv6Interface(IPv6Address):
self._prefixlen = self._max_prefixlen
return
addr = str(address).split('/')
addr = _split_optional_netmask(address)
IPv6Address.__init__(self, addr[0])
self.network = IPv6Network(address, strict=False)
self.netmask = self.network.netmask
@ -2003,7 +2045,8 @@ class IPv6Network(_BaseV6, _BaseNetwork):
# Constructing from a packed address
if isinstance(address, bytes):
if len(address) != 16:
raise AddressValueError(address)
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._prefixlen = self._max_prefixlen
@ -2012,10 +2055,7 @@ class IPv6Network(_BaseV6, _BaseNetwork):
# Assume input argument to be string or any object representation
# which converts into a formatted IP prefix string.
addr = str(address).split('/')
if len(addr) > 2:
raise AddressValueError(address)
addr = _split_optional_netmask(address)
self.network_address = IPv6Address(self._ip_int_from_string(addr[0]))
@ -2023,7 +2063,8 @@ class IPv6Network(_BaseV6, _BaseNetwork):
if self._is_valid_netmask(addr[1]):
self._prefixlen = int(addr[1])
else:
raise NetmaskValueError(addr[1])
raise NetmaskValueError('%r is not a valid netmask'
% addr[1])
else:
self._prefixlen = self._max_prefixlen

View file

@ -5,8 +5,415 @@
import unittest
import re
import contextlib
import ipaddress
class ErrorReporting(unittest.TestCase):
# One big change in ipaddress over the original ipaddr module is
# error reporting that tries to assume users *don't know the rules*
# for what constitutes an RFC compliant IP address
# Note that if the constructors are refactored so that addresses with
# multiple problems get classified differently, that's OK - just
# move the affected examples to the newly appropriate test case.
# The error reporting tests also cover a few corner cases that
# specifically *aren't* errors (such as leading zeroes in v6
# address parts) that don't have an obvious home in the main test
# suite
@contextlib.contextmanager
def assertCleanError(self, exc_type, details, *args):
"""
Ensure exception does not display a context by default
Wraps unittest.TestCase.assertRaisesRegex
"""
if args:
details = details % args
cm = self.assertRaisesRegex(exc_type, details)
with cm as exc:
yield exc
# Ensure we produce clean tracebacks on failure
if exc.exception.__context__ is not None:
self.assertTrue(exc.exception.__suppress_context__)
def assertAddressError(self, details, *args):
"""Ensure a clean AddressValueError"""
return self.assertCleanError(ipaddress.AddressValueError,
details, *args)
def assertNetmaskError(self, details, *args):
"""Ensure a clean NetmaskValueError"""
return self.assertCleanError(ipaddress.NetmaskValueError,
details, *args)
class AddressErrors_v4(ErrorReporting):
def test_empty_address(self):
with self.assertAddressError("Address cannot be empty"):
ipaddress.IPv4Address("")
def test_network_passed_as_address(self):
addr = "127.0.0.1/24"
with self.assertAddressError("Unexpected '/' in %r", addr):
ipaddress.IPv4Address(addr)
def test_bad_address_split(self):
def assertBadSplit(addr):
with self.assertAddressError("Expected 4 octets in %r", addr):
ipaddress.IPv4Address(addr)
assertBadSplit("127.0.1")
assertBadSplit("42.42.42.42.42")
assertBadSplit("42.42.42")
assertBadSplit("42.42")
assertBadSplit("42")
assertBadSplit("42..42.42.42")
assertBadSplit("42.42.42.42.")
assertBadSplit("42.42.42.42...")
assertBadSplit(".42.42.42.42")
assertBadSplit("...42.42.42.42")
assertBadSplit("016.016.016")
assertBadSplit("016.016")
assertBadSplit("016")
assertBadSplit("000")
assertBadSplit("0x0a.0x0a.0x0a")
assertBadSplit("0x0a.0x0a")
assertBadSplit("0x0a")
assertBadSplit(".")
assertBadSplit("bogus")
assertBadSplit("bogus.com")
assertBadSplit("1000")
assertBadSplit("1000000000000000")
assertBadSplit("192.168.0.1.com")
def test_empty_octet(self):
def assertBadOctet(addr):
with self.assertAddressError("Empty octet not permitted in %r",
addr):
ipaddress.IPv4Address(addr)
assertBadOctet("42..42.42")
assertBadOctet("...")
def test_invalid_characters(self):
def assertBadOctet(addr, octet):
msg = "Only decimal digits permitted in %r in %r" % (octet, addr)
with self.assertAddressError(re.escape(msg)):
ipaddress.IPv4Address(addr)
assertBadOctet("0x0a.0x0a.0x0a.0x0a", "0x0a")
assertBadOctet("42.42.42.-0", "-0")
assertBadOctet("42.42.42.+0", "+0")
assertBadOctet("42.42.42.-42", "-42")
assertBadOctet("+1.+2.+3.4", "+1")
assertBadOctet("1.2.3.4e0", "4e0")
assertBadOctet("1.2.3.4::", "4::")
assertBadOctet("1.a.2.3", "a")
def test_leading_zeros(self):
def assertBadOctet(addr, octet):
msg = "Ambiguous leading zero in %r not permitted in %r"
with self.assertAddressError(msg, octet, addr):
ipaddress.IPv4Address(addr)
assertBadOctet("016.016.016.016", "016")
assertBadOctet("001.000.008.016", "008")
self.assertEqual(ipaddress.IPv4Address("192.168.000.001"),
ipaddress.IPv4Address("192.168.0.1"))
def test_octet_limit(self):
def assertBadOctet(addr, octet):
msg = "Octet %d > 255 not permitted in %r"
with self.assertAddressError(msg, octet, addr):
ipaddress.IPv4Address(addr)
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("")
def test_network_passed_as_address(self):
addr = "::1/24"
with self.assertAddressError("Unexpected '/' in %r", addr):
ipaddress.IPv6Address(addr)
def test_bad_address_split_v6_not_enough_parts(self):
def assertBadSplit(addr):
msg = "At least 3 parts expected in %r"
with self.assertAddressError(msg, addr):
ipaddress.IPv6Address(addr)
assertBadSplit(":")
assertBadSplit(":1")
assertBadSplit("FEDC:9878")
def test_bad_address_split_v6_too_many_colons(self):
def assertBadSplit(addr):
msg = "At most 8 colons permitted in %r"
with self.assertAddressError(msg, addr):
ipaddress.IPv6Address(addr)
assertBadSplit("9:8:7:6:5:4:3::2:1")
assertBadSplit("10:9:8:7:6:5:4:3:2:1")
assertBadSplit("::8:7:6:5:4:3:2:1")
assertBadSplit("8:7:6:5:4:3:2:1::")
# A trailing IPv4 address is two parts
assertBadSplit("10:9:8:7:6:5:4:3:42.42.42.42")
def test_bad_address_split_v6_too_many_parts(self):
def assertBadSplit(addr):
msg = "Exactly 8 parts expected without '::' in %r"
with self.assertAddressError(msg, addr):
ipaddress.IPv6Address(addr)
assertBadSplit("3ffe:0:0:0:0:0:0:0:1")
assertBadSplit("9:8:7:6:5:4:3:2:1")
assertBadSplit("7:6:5:4:3:2:1")
# A trailing IPv4 address is two parts
assertBadSplit("9:8:7:6:5:4:3:42.42.42.42")
assertBadSplit("7:6:5:4:3:42.42.42.42")
def test_bad_address_split_v6_too_many_parts_with_double_colon(self):
def assertBadSplit(addr):
msg = "Expected at most 7 other parts with '::' in %r"
with self.assertAddressError(msg, addr):
ipaddress.IPv6Address(addr)
assertBadSplit("1:2:3:4::5:6:7:8")
def test_bad_address_split_v6_repeated_double_colon(self):
def assertBadSplit(addr):
msg = "At most one '::' permitted in %r"
with self.assertAddressError(msg, addr):
ipaddress.IPv6Address(addr)
assertBadSplit("3ffe::1::1")
assertBadSplit("1::2::3::4:5")
assertBadSplit("2001::db:::1")
assertBadSplit("3ffe::1::")
assertBadSplit("::3ffe::1")
assertBadSplit(":3ffe::1::1")
assertBadSplit("3ffe::1::1:")
assertBadSplit(":3ffe::1::1:")
assertBadSplit(":::")
assertBadSplit('2001:db8:::1')
def test_bad_address_split_v6_leading_colon(self):
def assertBadSplit(addr):
msg = "Leading ':' only permitted as part of '::' in %r"
with self.assertAddressError(msg, addr):
ipaddress.IPv6Address(addr)
assertBadSplit(":2001:db8::1")
assertBadSplit(":1:2:3:4:5:6:7")
assertBadSplit(":1:2:3:4:5:6:")
assertBadSplit(":6:5:4:3:2:1::")
def test_bad_address_split_v6_trailing_colon(self):
def assertBadSplit(addr):
msg = "Trailing ':' only permitted as part of '::' in %r"
with self.assertAddressError(msg, addr):
ipaddress.IPv6Address(addr)
assertBadSplit("2001:db8::1:")
assertBadSplit("1:2:3:4:5:6:7:")
assertBadSplit("::1.2.3.4:")
assertBadSplit("::7:6:5:4:3:2:")
def test_bad_v4_part_in(self):
def assertBadAddressPart(addr, v4_error):
with self.assertAddressError("%s in %r", v4_error, addr):
ipaddress.IPv6Address(addr)
assertBadAddressPart("3ffe::1.net", "Expected 4 octets in '1.net'")
assertBadAddressPart("3ffe::127.0.1",
"Expected 4 octets in '127.0.1'")
assertBadAddressPart("::1.2.3",
"Expected 4 octets in '1.2.3'")
assertBadAddressPart("::1.2.3.4.5",
"Expected 4 octets in '1.2.3.4.5'")
assertBadAddressPart("3ffe::1.1.1.net",
"Only decimal digits permitted in 'net' "
"in '1.1.1.net'")
def test_invalid_characters(self):
def assertBadPart(addr, part):
msg = "Only hex digits permitted in %r in %r" % (part, addr)
with self.assertAddressError(re.escape(msg)):
ipaddress.IPv6Address(addr)
assertBadPart("3ffe::goog", "goog")
assertBadPart("3ffe::-0", "-0")
assertBadPart("3ffe::+0", "+0")
assertBadPart("3ffe::-1", "-1")
assertBadPart("1.2.3.4::", "1.2.3.4")
assertBadPart('1234:axy::b', "axy")
def test_part_length(self):
def assertBadPart(addr, part):
msg = "At most 4 characters permitted in %r in %r"
with self.assertAddressError(msg, part, addr):
ipaddress.IPv6Address(addr)
assertBadPart("3ffe::10000", "10000")
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:
"""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):
self.factory(addr)
def test_address_errors(self):
def assertBadAddress(addr, details):
with self.assertAddressError(details):
self.factory(addr)
assertBadAddress("", "Address cannot be empty")
assertBadAddress("bogus", "Expected 4 octets")
assertBadAddress("google.com", "Expected 4 octets")
assertBadAddress("10/8", "Expected 4 octets")
assertBadAddress("::1.2.3.4", "Only decimal digits")
assertBadAddress("1.2.3.256", "256 > 255")
def test_netmask_errors(self):
def assertBadNetmask(addr, netmask):
msg = "%r is not a valid netmask"
with self.assertNetmaskError(msg % netmask):
self.factory("%s/%s" % (addr, netmask))
assertBadNetmask("1.2.3.4", "")
assertBadNetmask("1.2.3.4", "33")
assertBadNetmask("1.2.3.4", "254.254.255.256")
assertBadNetmask("1.1.1.1", "240.255.0.0")
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
class NetworkErrors_v4(ErrorReporting, NetmaskErrorsMixin_v4):
factory = ipaddress.IPv4Network
class NetmaskErrorsMixin_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):
self.factory(addr)
def test_address_errors(self):
def assertBadAddress(addr, details):
with self.assertAddressError(details):
self.factory(addr)
assertBadAddress("", "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")
assertBadAddress("1234:axy::b", "Only hex digits")
def test_netmask_errors(self):
def assertBadNetmask(addr, netmask):
msg = "%r is not a valid netmask"
with self.assertNetmaskError(msg % netmask):
self.factory("%s/%s" % (addr, netmask))
assertBadNetmask("::1", "")
assertBadNetmask("::1", "::1")
assertBadNetmask("::1", "1::")
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
class NetworkErrors_v6(ErrorReporting, NetmaskErrorsMixin_v6):
factory = ipaddress.IPv6Network
class FactoryFunctionErrors(ErrorReporting):
def assertFactoryError(self, factory, kind):
"""Ensure a clean ValueError with the expected message"""
addr = "camelot"
msg = '%r does not appear to be an IPv4 or IPv6 %s'
with self.assertCleanError(ValueError, msg, addr, kind):
factory(addr)
def test_ip_address(self):
self.assertFactoryError(ipaddress.ip_address, "address")
def test_ip_interface(self):
self.assertFactoryError(ipaddress.ip_interface, "interface")
def test_ip_network(self):
self.assertFactoryError(ipaddress.ip_network, "network")
class IpaddrUnitTest(unittest.TestCase):
@ -46,104 +453,6 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertRaises(ValueError, ipaddress.v6_int_to_packed,
2 ** ipaddress.IPV6LENGTH)
def testInvalidStringsInAddressFactory(self):
def AssertInvalidIP(ip_str):
with self.assertRaises(ValueError) as ex:
ipaddress.ip_address(ip_str)
self.assertIsNone(ex.exception.__context__)
AssertInvalidIP("")
AssertInvalidIP("016.016.016.016")
AssertInvalidIP("016.016.016")
AssertInvalidIP("016.016")
AssertInvalidIP("016")
AssertInvalidIP("000.000.000.000")
AssertInvalidIP("000")
AssertInvalidIP("0x0a.0x0a.0x0a.0x0a")
AssertInvalidIP("0x0a.0x0a.0x0a")
AssertInvalidIP("0x0a.0x0a")
AssertInvalidIP("0x0a")
AssertInvalidIP("42.42.42.42.42")
AssertInvalidIP("42.42.42")
AssertInvalidIP("42.42")
AssertInvalidIP("42")
AssertInvalidIP("42..42.42")
AssertInvalidIP("42..42.42.42")
AssertInvalidIP("42.42.42.42.")
AssertInvalidIP("42.42.42.42...")
AssertInvalidIP(".42.42.42.42")
AssertInvalidIP("...42.42.42.42")
AssertInvalidIP("42.42.42.-0")
AssertInvalidIP("42.42.42.+0")
AssertInvalidIP(".")
AssertInvalidIP("...")
AssertInvalidIP("bogus")
AssertInvalidIP("bogus.com")
AssertInvalidIP("192.168.0.1.com")
AssertInvalidIP("12345.67899.-54321.-98765")
AssertInvalidIP("257.0.0.0")
AssertInvalidIP("42.42.42.-42")
AssertInvalidIP("3ffe::1.net")
AssertInvalidIP("3ffe::1::1")
AssertInvalidIP("1::2::3::4:5")
AssertInvalidIP("::7:6:5:4:3:2:")
AssertInvalidIP(":6:5:4:3:2:1::")
AssertInvalidIP("2001::db:::1")
AssertInvalidIP("FEDC:9878")
AssertInvalidIP("+1.+2.+3.4")
AssertInvalidIP("1.2.3.4e0")
AssertInvalidIP("::7:6:5:4:3:2:1:0")
AssertInvalidIP("7:6:5:4:3:2:1:0::")
AssertInvalidIP("9:8:7:6:5:4:3::2:1")
AssertInvalidIP("0:1:2:3::4:5:6:7")
AssertInvalidIP("3ffe:0:0:0:0:0:0:0:1")
AssertInvalidIP("3ffe::10000")
AssertInvalidIP("3ffe::goog")
AssertInvalidIP("3ffe::-0")
AssertInvalidIP("3ffe::+0")
AssertInvalidIP("3ffe::-1")
AssertInvalidIP(":")
AssertInvalidIP(":::")
AssertInvalidIP("::1.2.3")
AssertInvalidIP("::1.2.3.4.5")
AssertInvalidIP("::1.2.3.4:")
AssertInvalidIP("1.2.3.4::")
AssertInvalidIP("2001:db8::1:")
AssertInvalidIP(":2001:db8::1")
AssertInvalidIP(":1:2:3:4:5:6:7")
AssertInvalidIP("1:2:3:4:5:6:7:")
AssertInvalidIP(":1:2:3:4:5:6:")
AssertInvalidIP("1000")
AssertInvalidIP("1000000000000000")
AssertInvalidIP("02001:db8::")
self.assertRaises(ValueError, ipaddress.ip_interface, 'bogus')
def testInvalidStringsInConstructors(self):
def AssertInvalidIP(ip_class, ip_str):
with self.assertRaises(ipaddress.AddressValueError) as ex:
ip_class(ip_str)
if ex.exception.__context__ is not None:
# Provide clean tracebacks by default
self.assertTrue(ex.exception.__suppress_context__)
AssertInvalidIP(ipaddress.IPv4Address, '127.0.0.1/32')
AssertInvalidIP(ipaddress.IPv4Address(1)._ip_int_from_string,
'1.a.2.3')
AssertInvalidIP(ipaddress.IPv4Interface, '')
AssertInvalidIP(ipaddress.IPv4Interface, 'google.com')
AssertInvalidIP(ipaddress.IPv6Address, '1234:axy::b')
AssertInvalidIP(ipaddress.IPv6Address, '2001:db8:::1')
AssertInvalidIP(ipaddress.IPv6Address, '2001:888888::1')
AssertInvalidIP(ipaddress.IPv4Interface, '::1.2.3.4')
AssertInvalidIP(ipaddress.IPv6Interface, '')
AssertInvalidIP(ipaddress.IPv6Interface, 'google.com')
AssertInvalidIP(ipaddress.IPv6Interface, '1.2.3.4')
AssertInvalidIP(ipaddress.IPv6Interface, 'cafe:cafe::/128/190')
AssertInvalidIP(ipaddress.IPv6Interface, '1234:axy::b')
def testInvalidHostmask(self):
self.assertFalse(ipaddress.IPv4Interface(1)._is_hostmask('1.a.2.3'))
def testInternals(self):
first, last = ipaddress._find_address_range([
ipaddress.IPv4Address('10.10.10.10'),
@ -278,22 +587,6 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(address('::'),
address(b'\x00' * 16))
def testIpFromPackedErrors(self):
def assertInvalidPackedAddress(f, length):
self.assertRaises(ValueError, f, b'\x00' * length)
assertInvalidPackedAddress(ipaddress.ip_address, 3)
assertInvalidPackedAddress(ipaddress.ip_address, 5)
assertInvalidPackedAddress(ipaddress.ip_address, 15)
assertInvalidPackedAddress(ipaddress.ip_address, 17)
assertInvalidPackedAddress(ipaddress.ip_interface, 3)
assertInvalidPackedAddress(ipaddress.ip_interface, 5)
assertInvalidPackedAddress(ipaddress.ip_interface, 15)
assertInvalidPackedAddress(ipaddress.ip_interface, 17)
assertInvalidPackedAddress(ipaddress.ip_network, 3)
assertInvalidPackedAddress(ipaddress.ip_network, 5)
assertInvalidPackedAddress(ipaddress.ip_network, 15)
assertInvalidPackedAddress(ipaddress.ip_network, 17)
def testGetIp(self):
self.assertEqual(int(self.ipv4_interface.ip), 16909060)
self.assertEqual(str(self.ipv4_interface.ip), '1.2.3.4')
@ -508,38 +801,6 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertFalse(ipaddress.IPv4Network('1.1.0.0/16').__contains__(
ipaddress.IPv4Network('1.0.0.0/15')))
def testBadAddress(self):
self.assertRaises(ipaddress.AddressValueError, ipaddress.IPv4Interface,
'poop')
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv4Interface, '1.2.3.256')
self.assertRaises(ipaddress.AddressValueError, ipaddress.IPv6Interface,
'poopv6')
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv4Interface, '1.2.3.4/32/24')
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv4Network, '1.2.3.4/32/24')
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv4Interface, '10/8')
self.assertRaises(ipaddress.AddressValueError,
ipaddress.IPv6Interface, '10/8')
def testBadNetMask(self):
self.assertRaises(ipaddress.NetmaskValueError,
ipaddress.IPv4Interface, '1.2.3.4/')
self.assertRaises(ipaddress.NetmaskValueError,
ipaddress.IPv4Interface, '1.2.3.4/33')
self.assertRaises(ipaddress.NetmaskValueError,
ipaddress.IPv4Interface, '1.2.3.4/254.254.255.256')
self.assertRaises(ipaddress.NetmaskValueError,
ipaddress.IPv4Interface, '1.1.1.1/240.255.0.0')
self.assertRaises(ipaddress.NetmaskValueError,
ipaddress.IPv6Interface, '::1/')
self.assertRaises(ipaddress.NetmaskValueError,
ipaddress.IPv6Interface, '::1/129')
def testNth(self):
self.assertEqual(str(self.ipv4_network[5]), '1.2.3.5')
self.assertRaises(IndexError, self.ipv4_network.__getitem__, 256)

View file

@ -23,6 +23,10 @@ Core and Builtins
Library
-------
- Issue #14814: ipaddress now provides more informative error messages when
constructing instances directly (changes permitted during beta due to
provisional API status)
- Issue #15247: FileIO now raises an error when given a file descriptor
pointing to a directory.