from typing import Optional def parse_ipv4(text: str) -> int: """The address as an unsigned 32-bit number. Only the strict dotted quad is accepted. The C library's inet_aton also reads "010.1.1.1" as octal 8.1.1.1, "0x7f.1" as hex and "127.1" as 127.0.0.1; a validator that agrees with one reader and a socket library that agrees with the other is how an allow-list gets bypassed, so all of those are refused. """ value = ipv4_value(text) if value is None: raise ValueError('"%s" is not a dotted-quad IPv4 address' % (text,)) return value # Exported for is_ipv4 and for capabilities that parse IPv4 text without # wanting an error; not part of the group's contract. def ipv4_value(text: object) -> Optional[int]: """The address as a number, or None when the text is not a strict dotted quad.""" if not isinstance(text, str): return None parts = text.split(".") if len(parts) != 4: return None value = 0 for part in parts: if len(part) < 1 or len(part) > 3: return None # "0" <= ch <= "9" rather than isdigit(), which accepts other scripts' digits. for ch in part: if not ("0" <= ch <= "9"): return None # A leading zero is where octal readers and decimal readers part company. if len(part) > 1 and part[0] == "0": return None octet = int(part) if octet > 255: return None value = value * 256 + octet return value