Functional Weave
Code in Rust

net.ipv6@1.0.0

impl/python/parse_ipv6.py

2,279 bytes · the Python implementation · view raw

from typing import List, Optional, Sequence

from .net_ipv4_parse_ipv4 import ipv4_value

_ALLOWED = frozenset("0123456789abcdefABCDEF:.")


def parse_ipv6(text: str) -> List[int]:
    """The eight 16-bit groups of an IPv6 address written as RFC 4291 allows.

    Hex groups of one to four digits in either case, at most one "::" standing
    for one or more zero groups, and optionally a dotted IPv4 address as the
    last 32 bits. A zone id ("%eth0") or a prefix ("/64") is not part of an
    address and is refused, as is anything around it (spaces, newlines).
    """
    groups = ipv6_groups(text)
    if groups is None:
        raise ValueError('"%s" is not an IPv6 address' % (text,))
    return groups


# Exported for is_ipv6 and for capabilities that parse IPv6 text without
# wanting an error; not part of the group's contract.


def ipv6_groups(text: object) -> Optional[List[int]]:
    """The eight groups, or None when the text is not an IPv6 address."""
    if not isinstance(text, str) or text == "":
        return None
    for ch in text:
        if ch not in _ALLOWED:
            return None
    at = text.find("::")
    if at < 0:
        groups = _pieces(text.split(":"), True)
        return groups if groups is not None and len(groups) == 8 else None
    before = text[:at]
    after = text[at + 2:]
    if "::" in after:
        return None
    head = _pieces(before.split(":") if before else [], False)
    tail = _pieces(after.split(":") if after else [], True)
    if head is None or tail is None:
        return None
    # "::" stands for at least one group, so at most seven are written.
    if len(head) + len(tail) > 7:
        return None
    return head + [0] * (8 - len(head) - len(tail)) + tail


def _pieces(parts: Sequence[str], may_end_with_ipv4: bool) -> Optional[List[int]]:
    groups: List[int] = []
    for i, part in enumerate(parts):
        if "." in part:
            if not may_end_with_ipv4 or i != len(parts) - 1:
                return None
            v4 = ipv4_value(part)
            if v4 is None:
                return None
            groups.extend([v4 // 65536, v4 % 65536])
            continue
        if len(part) < 1 or len(part) > 4:
            return None
        groups.append(int(part, 16))
    return groups