diff -r 6d8e01f2169c Doc/library/ipaddress.rst --- a/Doc/library/ipaddress.rst Wed Aug 27 01:58:57 2014 -0400 +++ b/Doc/library/ipaddress.rst Wed Aug 27 15:02:52 2014 +0200 @@ -93,7 +93,7 @@ also implemented by :class:`IPv4Address` objects, in order to make it easier to write code that handles both IP versions correctly. -.. class:: IPv4Address(address) +.. class:: IPv4Address(address, decimal_only=False) Construct an IPv4 address. An :exc:`AddressValueError` is raised if *address* is not a valid IPv4 address. @@ -104,7 +104,8 @@ the inclusive range 0-255, separated by dots (e.g. ``192.168.0.1``). Each integer represents an octet (byte) in the address. Leading zeroes are tolerated only for values less than 8 (as there is no ambiguity - between the decimal and octal interpretations of such strings). + between the decimal and octal interpretations of such strings). If + decimal_only is set to True leading zeroes are never permitted. 2. An integer that fits into 32 bits. 3. An integer packed into a :class:`bytes` object of length 4 (most significant octet first). diff -r 6d8e01f2169c Lib/ipaddress.py --- a/Lib/ipaddress.py Wed Aug 27 01:58:57 2014 -0400 +++ b/Lib/ipaddress.py Wed Aug 27 15:02:52 2014 +0200 @@ -1126,11 +1126,12 @@ return cls._netmask_cache[arg] @classmethod - def _ip_int_from_string(cls, ip_str): + def _ip_int_from_string(cls, ip_str, decimal_only=False): """Turn the given IP string into an integer for comparison. Args: ip_str: A string, the IP ip_str. + decimal_only: All parts must be decimal Returns: The IP ip_str as an integer. @@ -1147,16 +1148,18 @@ raise AddressValueError("Expected 4 octets in %r" % ip_str) try: - return int.from_bytes(map(cls._parse_octet, octets), 'big') + return int.from_bytes((cls._parse_octet(octet, decimal_only) + for octet in octets), 'big') except ValueError as exc: raise AddressValueError("%s in %r" % (exc, ip_str)) from None @classmethod - def _parse_octet(cls, octet_str): + def _parse_octet(cls, octet_str, decimal_only=False): """Convert a decimal octet into an integer. Args: octet_str: A string, the number to parse. + decimal_only: The string must be a decimal number. Returns: The octet as an integer. @@ -1176,6 +1179,12 @@ if len(octet_str) > 3: msg = "At most 3 characters permitted in %r" raise ValueError(msg % octet_str) + # For example IPv6 addresses don't allow octal numbers in the IPv4 + # address part. To avoid ambiguity leading zeroes are not permitted + if decimal_only and len(octet_str) > 1 and octet_str[0] == '0': + msg = ("%r has leading zeroes, which could be interpreted as an " + "octal number, but only decimal numbers are permitted") + raise ValueError(msg % octet_str) # Convert to integer (we know digits are legal) octet_int = int(octet_str, 10) # Any octets that look like they *might* be written in octal, @@ -1275,7 +1284,7 @@ """Represent and manipulate single IPv4 Addresses.""" - def __init__(self, address): + def __init__(self, address, decimal_only=False): """ Args: @@ -1287,6 +1296,10 @@ IPv4Address(int(IPv4Address('192.0.2.1'))) == IPv4Address('192.0.2.1') + decimal_only: A boolean which determines if each part must be an + decimal number. IPv6 addresses with IPv4 addresses at the end + require all parts to be only decimal numbers. + Raises: AddressValueError: If ipaddress isn't a valid IPv4 address. @@ -1309,7 +1322,7 @@ # Assume input argument to be string or any object representation # which converts into a formatted IP string. addr_str = str(address) - self._ip = self._ip_int_from_string(addr_str) + self._ip = self._ip_int_from_string(addr_str, decimal_only) @property def packed(self): @@ -1677,7 +1690,7 @@ # If the address has an IPv4-style suffix, convert it to hexadecimal. if '.' in parts[-1]: try: - ipv4_int = IPv4Address(parts.pop())._ip + ipv4_int = IPv4Address(parts.pop(), True)._ip except AddressValueError as exc: raise AddressValueError("%s in %r" % (exc, ip_str)) from None parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF)) diff -r 6d8e01f2169c Lib/test/test_ipaddress.py --- a/Lib/test/test_ipaddress.py Wed Aug 27 01:58:57 2014 -0400 +++ b/Lib/test/test_ipaddress.py Wed Aug 27 15:02:52 2014 +0200 @@ -351,6 +351,14 @@ assertBadAddressPart("3ffe::1.1.1.net", "Only decimal digits permitted in 'net' " "in '1.1.1.net'") + assertBadAddressPart(u"::1.0.0.00", + "u?'00' has leading zeroes, which could be " + "interpreted as an octal number, but only " + "decimal numbers are permitted in u?'1.0.0.00'") + assertBadAddressPart(u"::1.0.0.000", + "u?'000' has leading zeroes, which could be " + "interpreted as an octal number, but only " + "decimal numbers are permitted in u?'1.0.0.000'") def test_invalid_characters(self): def assertBadPart(addr, part):