about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/dns/name.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/dns/name.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/dns/name.py')
-rw-r--r--.venv/lib/python3.12/site-packages/dns/name.py1284
1 files changed, 1284 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/dns/name.py b/.venv/lib/python3.12/site-packages/dns/name.py
new file mode 100644
index 00000000..f79f0d0f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/dns/name.py
@@ -0,0 +1,1284 @@
+# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
+
+# Copyright (C) 2001-2017 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose with or without fee is hereby granted,
+# provided that the above copyright notice and this permission notice
+# appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""DNS Names.
+"""
+
+import copy
+import encodings.idna  # type: ignore
+import functools
+import struct
+from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union
+
+import dns._features
+import dns.enum
+import dns.exception
+import dns.immutable
+import dns.wire
+
+if dns._features.have("idna"):
+    import idna  # type: ignore
+
+    have_idna_2008 = True
+else:  # pragma: no cover
+    have_idna_2008 = False
+
+CompressType = Dict["Name", int]
+
+
+class NameRelation(dns.enum.IntEnum):
+    """Name relation result from fullcompare()."""
+
+    # This is an IntEnum for backwards compatibility in case anyone
+    # has hardwired the constants.
+
+    #: The compared names have no relationship to each other.
+    NONE = 0
+    #: the first name is a superdomain of the second.
+    SUPERDOMAIN = 1
+    #: The first name is a subdomain of the second.
+    SUBDOMAIN = 2
+    #: The compared names are equal.
+    EQUAL = 3
+    #: The compared names have a common ancestor.
+    COMMONANCESTOR = 4
+
+    @classmethod
+    def _maximum(cls):
+        return cls.COMMONANCESTOR  # pragma: no cover
+
+    @classmethod
+    def _short_name(cls):
+        return cls.__name__  # pragma: no cover
+
+
+# Backwards compatibility
+NAMERELN_NONE = NameRelation.NONE
+NAMERELN_SUPERDOMAIN = NameRelation.SUPERDOMAIN
+NAMERELN_SUBDOMAIN = NameRelation.SUBDOMAIN
+NAMERELN_EQUAL = NameRelation.EQUAL
+NAMERELN_COMMONANCESTOR = NameRelation.COMMONANCESTOR
+
+
+class EmptyLabel(dns.exception.SyntaxError):
+    """A DNS label is empty."""
+
+
+class BadEscape(dns.exception.SyntaxError):
+    """An escaped code in a text format of DNS name is invalid."""
+
+
+class BadPointer(dns.exception.FormError):
+    """A DNS compression pointer points forward instead of backward."""
+
+
+class BadLabelType(dns.exception.FormError):
+    """The label type in DNS name wire format is unknown."""
+
+
+class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):
+    """An attempt was made to convert a non-absolute name to
+    wire when there was also a non-absolute (or missing) origin."""
+
+
+class NameTooLong(dns.exception.FormError):
+    """A DNS name is > 255 octets long."""
+
+
+class LabelTooLong(dns.exception.SyntaxError):
+    """A DNS label is > 63 octets long."""
+
+
+class AbsoluteConcatenation(dns.exception.DNSException):
+    """An attempt was made to append anything other than the
+    empty name to an absolute DNS name."""
+
+
+class NoParent(dns.exception.DNSException):
+    """An attempt was made to get the parent of the root name
+    or the empty name."""
+
+
+class NoIDNA2008(dns.exception.DNSException):
+    """IDNA 2008 processing was requested but the idna module is not
+    available."""
+
+
+class IDNAException(dns.exception.DNSException):
+    """IDNA processing raised an exception."""
+
+    supp_kwargs = {"idna_exception"}
+    fmt = "IDNA processing exception: {idna_exception}"
+
+    # We do this as otherwise mypy complains about unexpected keyword argument
+    # idna_exception
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+
+class NeedSubdomainOfOrigin(dns.exception.DNSException):
+    """An absolute name was provided that is not a subdomain of the specified origin."""
+
+
+_escaped = b'"().;\\@$'
+_escaped_text = '"().;\\@$'
+
+
+def _escapify(label: Union[bytes, str]) -> str:
+    """Escape the characters in label which need it.
+    @returns: the escaped string
+    @rtype: string"""
+    if isinstance(label, bytes):
+        # Ordinary DNS label mode.  Escape special characters and values
+        # < 0x20 or > 0x7f.
+        text = ""
+        for c in label:
+            if c in _escaped:
+                text += "\\" + chr(c)
+            elif c > 0x20 and c < 0x7F:
+                text += chr(c)
+            else:
+                text += "\\%03d" % c
+        return text
+
+    # Unicode label mode.  Escape only special characters and values < 0x20
+    text = ""
+    for uc in label:
+        if uc in _escaped_text:
+            text += "\\" + uc
+        elif uc <= "\x20":
+            text += "\\%03d" % ord(uc)
+        else:
+            text += uc
+    return text
+
+
+class IDNACodec:
+    """Abstract base class for IDNA encoder/decoders."""
+
+    def __init__(self):
+        pass
+
+    def is_idna(self, label: bytes) -> bool:
+        return label.lower().startswith(b"xn--")
+
+    def encode(self, label: str) -> bytes:
+        raise NotImplementedError  # pragma: no cover
+
+    def decode(self, label: bytes) -> str:
+        # We do not apply any IDNA policy on decode.
+        if self.is_idna(label):
+            try:
+                slabel = label[4:].decode("punycode")
+                return _escapify(slabel)
+            except Exception as e:
+                raise IDNAException(idna_exception=e)
+        else:
+            return _escapify(label)
+
+
+class IDNA2003Codec(IDNACodec):
+    """IDNA 2003 encoder/decoder."""
+
+    def __init__(self, strict_decode: bool = False):
+        """Initialize the IDNA 2003 encoder/decoder.
+
+        *strict_decode* is a ``bool``. If `True`, then IDNA2003 checking
+        is done when decoding.  This can cause failures if the name
+        was encoded with IDNA2008.  The default is `False`.
+        """
+
+        super().__init__()
+        self.strict_decode = strict_decode
+
+    def encode(self, label: str) -> bytes:
+        """Encode *label*."""
+
+        if label == "":
+            return b""
+        try:
+            return encodings.idna.ToASCII(label)
+        except UnicodeError:
+            raise LabelTooLong
+
+    def decode(self, label: bytes) -> str:
+        """Decode *label*."""
+        if not self.strict_decode:
+            return super().decode(label)
+        if label == b"":
+            return ""
+        try:
+            return _escapify(encodings.idna.ToUnicode(label))
+        except Exception as e:
+            raise IDNAException(idna_exception=e)
+
+
+class IDNA2008Codec(IDNACodec):
+    """IDNA 2008 encoder/decoder."""
+
+    def __init__(
+        self,
+        uts_46: bool = False,
+        transitional: bool = False,
+        allow_pure_ascii: bool = False,
+        strict_decode: bool = False,
+    ):
+        """Initialize the IDNA 2008 encoder/decoder.
+
+        *uts_46* is a ``bool``.  If True, apply Unicode IDNA
+        compatibility processing as described in Unicode Technical
+        Standard #46 (https://unicode.org/reports/tr46/).
+        If False, do not apply the mapping.  The default is False.
+
+        *transitional* is a ``bool``: If True, use the
+        "transitional" mode described in Unicode Technical Standard
+        #46.  The default is False.
+
+        *allow_pure_ascii* is a ``bool``.  If True, then a label which
+        consists of only ASCII characters is allowed.  This is less
+        strict than regular IDNA 2008, but is also necessary for mixed
+        names, e.g. a name with starting with "_sip._tcp." and ending
+        in an IDN suffix which would otherwise be disallowed.  The
+        default is False.
+
+        *strict_decode* is a ``bool``: If True, then IDNA2008 checking
+        is done when decoding.  This can cause failures if the name
+        was encoded with IDNA2003.  The default is False.
+        """
+        super().__init__()
+        self.uts_46 = uts_46
+        self.transitional = transitional
+        self.allow_pure_ascii = allow_pure_ascii
+        self.strict_decode = strict_decode
+
+    def encode(self, label: str) -> bytes:
+        if label == "":
+            return b""
+        if self.allow_pure_ascii and is_all_ascii(label):
+            encoded = label.encode("ascii")
+            if len(encoded) > 63:
+                raise LabelTooLong
+            return encoded
+        if not have_idna_2008:
+            raise NoIDNA2008
+        try:
+            if self.uts_46:
+                # pylint: disable=possibly-used-before-assignment
+                label = idna.uts46_remap(label, False, self.transitional)
+            return idna.alabel(label)
+        except idna.IDNAError as e:
+            if e.args[0] == "Label too long":
+                raise LabelTooLong
+            else:
+                raise IDNAException(idna_exception=e)
+
+    def decode(self, label: bytes) -> str:
+        if not self.strict_decode:
+            return super().decode(label)
+        if label == b"":
+            return ""
+        if not have_idna_2008:
+            raise NoIDNA2008
+        try:
+            ulabel = idna.ulabel(label)
+            if self.uts_46:
+                ulabel = idna.uts46_remap(ulabel, False, self.transitional)
+            return _escapify(ulabel)
+        except (idna.IDNAError, UnicodeError) as e:
+            raise IDNAException(idna_exception=e)
+
+
+IDNA_2003_Practical = IDNA2003Codec(False)
+IDNA_2003_Strict = IDNA2003Codec(True)
+IDNA_2003 = IDNA_2003_Practical
+IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)
+IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)
+IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)
+IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)
+IDNA_2008 = IDNA_2008_Practical
+
+
+def _validate_labels(labels: Tuple[bytes, ...]) -> None:
+    """Check for empty labels in the middle of a label sequence,
+    labels that are too long, and for too many labels.
+
+    Raises ``dns.name.NameTooLong`` if the name as a whole is too long.
+
+    Raises ``dns.name.EmptyLabel`` if a label is empty (i.e. the root
+    label) and appears in a position other than the end of the label
+    sequence
+
+    """
+
+    l = len(labels)
+    total = 0
+    i = -1
+    j = 0
+    for label in labels:
+        ll = len(label)
+        total += ll + 1
+        if ll > 63:
+            raise LabelTooLong
+        if i < 0 and label == b"":
+            i = j
+        j += 1
+    if total > 255:
+        raise NameTooLong
+    if i >= 0 and i != l - 1:
+        raise EmptyLabel
+
+
+def _maybe_convert_to_binary(label: Union[bytes, str]) -> bytes:
+    """If label is ``str``, convert it to ``bytes``.  If it is already
+    ``bytes`` just return it.
+
+    """
+
+    if isinstance(label, bytes):
+        return label
+    if isinstance(label, str):
+        return label.encode()
+    raise ValueError  # pragma: no cover
+
+
+@dns.immutable.immutable
+class Name:
+    """A DNS name.
+
+    The dns.name.Name class represents a DNS name as a tuple of
+    labels.  Each label is a ``bytes`` in DNS wire format.  Instances
+    of the class are immutable.
+    """
+
+    __slots__ = ["labels"]
+
+    def __init__(self, labels: Iterable[Union[bytes, str]]):
+        """*labels* is any iterable whose values are ``str`` or ``bytes``."""
+
+        blabels = [_maybe_convert_to_binary(x) for x in labels]
+        self.labels = tuple(blabels)
+        _validate_labels(self.labels)
+
+    def __copy__(self):
+        return Name(self.labels)
+
+    def __deepcopy__(self, memo):
+        return Name(copy.deepcopy(self.labels, memo))
+
+    def __getstate__(self):
+        # Names can be pickled
+        return {"labels": self.labels}
+
+    def __setstate__(self, state):
+        super().__setattr__("labels", state["labels"])
+        _validate_labels(self.labels)
+
+    def is_absolute(self) -> bool:
+        """Is the most significant label of this name the root label?
+
+        Returns a ``bool``.
+        """
+
+        return len(self.labels) > 0 and self.labels[-1] == b""
+
+    def is_wild(self) -> bool:
+        """Is this name wild?  (I.e. Is the least significant label '*'?)
+
+        Returns a ``bool``.
+        """
+
+        return len(self.labels) > 0 and self.labels[0] == b"*"
+
+    def __hash__(self) -> int:
+        """Return a case-insensitive hash of the name.
+
+        Returns an ``int``.
+        """
+
+        h = 0
+        for label in self.labels:
+            for c in label.lower():
+                h += (h << 3) + c
+        return h
+
+    def fullcompare(self, other: "Name") -> Tuple[NameRelation, int, int]:
+        """Compare two names, returning a 3-tuple
+        ``(relation, order, nlabels)``.
+
+        *relation* describes the relation ship between the names,
+        and is one of: ``dns.name.NameRelation.NONE``,
+        ``dns.name.NameRelation.SUPERDOMAIN``, ``dns.name.NameRelation.SUBDOMAIN``,
+        ``dns.name.NameRelation.EQUAL``, or ``dns.name.NameRelation.COMMONANCESTOR``.
+
+        *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==
+        0 if *self* == *other*.  A relative name is always less than an
+        absolute name.  If both names have the same relativity, then
+        the DNSSEC order relation is used to order them.
+
+        *nlabels* is the number of significant labels that the two names
+        have in common.
+
+        Here are some examples.  Names ending in "." are absolute names,
+        those not ending in "." are relative names.
+
+        =============  =============  ===========  =====  =======
+        self           other          relation     order  nlabels
+        =============  =============  ===========  =====  =======
+        www.example.   www.example.   equal        0      3
+        www.example.   example.       subdomain    > 0    2
+        example.       www.example.   superdomain  < 0    2
+        example1.com.  example2.com.  common anc.  < 0    2
+        example1       example2.      none         < 0    0
+        example1.      example2       none         > 0    0
+        =============  =============  ===========  =====  =======
+        """
+
+        sabs = self.is_absolute()
+        oabs = other.is_absolute()
+        if sabs != oabs:
+            if sabs:
+                return (NameRelation.NONE, 1, 0)
+            else:
+                return (NameRelation.NONE, -1, 0)
+        l1 = len(self.labels)
+        l2 = len(other.labels)
+        ldiff = l1 - l2
+        if ldiff < 0:
+            l = l1
+        else:
+            l = l2
+
+        order = 0
+        nlabels = 0
+        namereln = NameRelation.NONE
+        while l > 0:
+            l -= 1
+            l1 -= 1
+            l2 -= 1
+            label1 = self.labels[l1].lower()
+            label2 = other.labels[l2].lower()
+            if label1 < label2:
+                order = -1
+                if nlabels > 0:
+                    namereln = NameRelation.COMMONANCESTOR
+                return (namereln, order, nlabels)
+            elif label1 > label2:
+                order = 1
+                if nlabels > 0:
+                    namereln = NameRelation.COMMONANCESTOR
+                return (namereln, order, nlabels)
+            nlabels += 1
+        order = ldiff
+        if ldiff < 0:
+            namereln = NameRelation.SUPERDOMAIN
+        elif ldiff > 0:
+            namereln = NameRelation.SUBDOMAIN
+        else:
+            namereln = NameRelation.EQUAL
+        return (namereln, order, nlabels)
+
+    def is_subdomain(self, other: "Name") -> bool:
+        """Is self a subdomain of other?
+
+        Note that the notion of subdomain includes equality, e.g.
+        "dnspython.org" is a subdomain of itself.
+
+        Returns a ``bool``.
+        """
+
+        (nr, _, _) = self.fullcompare(other)
+        if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL:
+            return True
+        return False
+
+    def is_superdomain(self, other: "Name") -> bool:
+        """Is self a superdomain of other?
+
+        Note that the notion of superdomain includes equality, e.g.
+        "dnspython.org" is a superdomain of itself.
+
+        Returns a ``bool``.
+        """
+
+        (nr, _, _) = self.fullcompare(other)
+        if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL:
+            return True
+        return False
+
+    def canonicalize(self) -> "Name":
+        """Return a name which is equal to the current name, but is in
+        DNSSEC canonical form.
+        """
+
+        return Name([x.lower() for x in self.labels])
+
+    def __eq__(self, other):
+        if isinstance(other, Name):
+            return self.fullcompare(other)[1] == 0
+        else:
+            return False
+
+    def __ne__(self, other):
+        if isinstance(other, Name):
+            return self.fullcompare(other)[1] != 0
+        else:
+            return True
+
+    def __lt__(self, other):
+        if isinstance(other, Name):
+            return self.fullcompare(other)[1] < 0
+        else:
+            return NotImplemented
+
+    def __le__(self, other):
+        if isinstance(other, Name):
+            return self.fullcompare(other)[1] <= 0
+        else:
+            return NotImplemented
+
+    def __ge__(self, other):
+        if isinstance(other, Name):
+            return self.fullcompare(other)[1] >= 0
+        else:
+            return NotImplemented
+
+    def __gt__(self, other):
+        if isinstance(other, Name):
+            return self.fullcompare(other)[1] > 0
+        else:
+            return NotImplemented
+
+    def __repr__(self):
+        return "<DNS name " + self.__str__() + ">"
+
+    def __str__(self):
+        return self.to_text(False)
+
+    def to_text(self, omit_final_dot: bool = False) -> str:
+        """Convert name to DNS text format.
+
+        *omit_final_dot* is a ``bool``.  If True, don't emit the final
+        dot (denoting the root label) for absolute names.  The default
+        is False.
+
+        Returns a ``str``.
+        """
+
+        if len(self.labels) == 0:
+            return "@"
+        if len(self.labels) == 1 and self.labels[0] == b"":
+            return "."
+        if omit_final_dot and self.is_absolute():
+            l = self.labels[:-1]
+        else:
+            l = self.labels
+        s = ".".join(map(_escapify, l))
+        return s
+
+    def to_unicode(
+        self, omit_final_dot: bool = False, idna_codec: Optional[IDNACodec] = None
+    ) -> str:
+        """Convert name to Unicode text format.
+
+        IDN ACE labels are converted to Unicode.
+
+        *omit_final_dot* is a ``bool``.  If True, don't emit the final
+        dot (denoting the root label) for absolute names.  The default
+        is False.
+        *idna_codec* specifies the IDNA encoder/decoder.  If None, the
+        dns.name.IDNA_2003_Practical encoder/decoder is used.
+        The IDNA_2003_Practical decoder does
+        not impose any policy, it just decodes punycode, so if you
+        don't want checking for compliance, you can use this decoder
+        for IDNA2008 as well.
+
+        Returns a ``str``.
+        """
+
+        if len(self.labels) == 0:
+            return "@"
+        if len(self.labels) == 1 and self.labels[0] == b"":
+            return "."
+        if omit_final_dot and self.is_absolute():
+            l = self.labels[:-1]
+        else:
+            l = self.labels
+        if idna_codec is None:
+            idna_codec = IDNA_2003_Practical
+        return ".".join([idna_codec.decode(x) for x in l])
+
+    def to_digestable(self, origin: Optional["Name"] = None) -> bytes:
+        """Convert name to a format suitable for digesting in hashes.
+
+        The name is canonicalized and converted to uncompressed wire
+        format.  All names in wire format are absolute.  If the name
+        is a relative name, then an origin must be supplied.
+
+        *origin* is a ``dns.name.Name`` or ``None``.  If the name is
+        relative and origin is not ``None``, then origin will be appended
+        to the name.
+
+        Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
+        relative and no origin was provided.
+
+        Returns a ``bytes``.
+        """
+
+        digest = self.to_wire(origin=origin, canonicalize=True)
+        assert digest is not None
+        return digest
+
+    def to_wire(
+        self,
+        file: Optional[Any] = None,
+        compress: Optional[CompressType] = None,
+        origin: Optional["Name"] = None,
+        canonicalize: bool = False,
+    ) -> Optional[bytes]:
+        """Convert name to wire format, possibly compressing it.
+
+        *file* is the file where the name is emitted (typically an
+        io.BytesIO file).  If ``None`` (the default), a ``bytes``
+        containing the wire name will be returned.
+
+        *compress*, a ``dict``, is the compression table to use.  If
+        ``None`` (the default), names will not be compressed.  Note that
+        the compression code assumes that compression offset 0 is the
+        start of *file*, and thus compression will not be correct
+        if this is not the case.
+
+        *origin* is a ``dns.name.Name`` or ``None``.  If the name is
+        relative and origin is not ``None``, then *origin* will be appended
+        to it.
+
+        *canonicalize*, a ``bool``, indicates whether the name should
+        be canonicalized; that is, converted to a format suitable for
+        digesting in hashes.
+
+        Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
+        relative and no origin was provided.
+
+        Returns a ``bytes`` or ``None``.
+        """
+
+        if file is None:
+            out = bytearray()
+            for label in self.labels:
+                out.append(len(label))
+                if canonicalize:
+                    out += label.lower()
+                else:
+                    out += label
+            if not self.is_absolute():
+                if origin is None or not origin.is_absolute():
+                    raise NeedAbsoluteNameOrOrigin
+                for label in origin.labels:
+                    out.append(len(label))
+                    if canonicalize:
+                        out += label.lower()
+                    else:
+                        out += label
+            return bytes(out)
+
+        labels: Iterable[bytes]
+        if not self.is_absolute():
+            if origin is None or not origin.is_absolute():
+                raise NeedAbsoluteNameOrOrigin
+            labels = list(self.labels)
+            labels.extend(list(origin.labels))
+        else:
+            labels = self.labels
+        i = 0
+        for label in labels:
+            n = Name(labels[i:])
+            i += 1
+            if compress is not None:
+                pos = compress.get(n)
+            else:
+                pos = None
+            if pos is not None:
+                value = 0xC000 + pos
+                s = struct.pack("!H", value)
+                file.write(s)
+                break
+            else:
+                if compress is not None and len(n) > 1:
+                    pos = file.tell()
+                    if pos <= 0x3FFF:
+                        compress[n] = pos
+                l = len(label)
+                file.write(struct.pack("!B", l))
+                if l > 0:
+                    if canonicalize:
+                        file.write(label.lower())
+                    else:
+                        file.write(label)
+        return None
+
+    def __len__(self) -> int:
+        """The length of the name (in labels).
+
+        Returns an ``int``.
+        """
+
+        return len(self.labels)
+
+    def __getitem__(self, index):
+        return self.labels[index]
+
+    def __add__(self, other):
+        return self.concatenate(other)
+
+    def __sub__(self, other):
+        return self.relativize(other)
+
+    def split(self, depth: int) -> Tuple["Name", "Name"]:
+        """Split a name into a prefix and suffix names at the specified depth.
+
+        *depth* is an ``int`` specifying the number of labels in the suffix
+
+        Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the
+        name.
+
+        Returns the tuple ``(prefix, suffix)``.
+        """
+
+        l = len(self.labels)
+        if depth == 0:
+            return (self, dns.name.empty)
+        elif depth == l:
+            return (dns.name.empty, self)
+        elif depth < 0 or depth > l:
+            raise ValueError("depth must be >= 0 and <= the length of the name")
+        return (Name(self[:-depth]), Name(self[-depth:]))
+
+    def concatenate(self, other: "Name") -> "Name":
+        """Return a new name which is the concatenation of self and other.
+
+        Raises ``dns.name.AbsoluteConcatenation`` if the name is
+        absolute and *other* is not the empty name.
+
+        Returns a ``dns.name.Name``.
+        """
+
+        if self.is_absolute() and len(other) > 0:
+            raise AbsoluteConcatenation
+        labels = list(self.labels)
+        labels.extend(list(other.labels))
+        return Name(labels)
+
+    def relativize(self, origin: "Name") -> "Name":
+        """If the name is a subdomain of *origin*, return a new name which is
+        the name relative to origin.  Otherwise return the name.
+
+        For example, relativizing ``www.dnspython.org.`` to origin
+        ``dnspython.org.`` returns the name ``www``.  Relativizing ``example.``
+        to origin ``dnspython.org.`` returns ``example.``.
+
+        Returns a ``dns.name.Name``.
+        """
+
+        if origin is not None and self.is_subdomain(origin):
+            return Name(self[: -len(origin)])
+        else:
+            return self
+
+    def derelativize(self, origin: "Name") -> "Name":
+        """If the name is a relative name, return a new name which is the
+        concatenation of the name and origin.  Otherwise return the name.
+
+        For example, derelativizing ``www`` to origin ``dnspython.org.``
+        returns the name ``www.dnspython.org.``.  Derelativizing ``example.``
+        to origin ``dnspython.org.`` returns ``example.``.
+
+        Returns a ``dns.name.Name``.
+        """
+
+        if not self.is_absolute():
+            return self.concatenate(origin)
+        else:
+            return self
+
+    def choose_relativity(
+        self, origin: Optional["Name"] = None, relativize: bool = True
+    ) -> "Name":
+        """Return a name with the relativity desired by the caller.
+
+        If *origin* is ``None``, then the name is returned.
+        Otherwise, if *relativize* is ``True`` the name is
+        relativized, and if *relativize* is ``False`` the name is
+        derelativized.
+
+        Returns a ``dns.name.Name``.
+        """
+
+        if origin:
+            if relativize:
+                return self.relativize(origin)
+            else:
+                return self.derelativize(origin)
+        else:
+            return self
+
+    def parent(self) -> "Name":
+        """Return the parent of the name.
+
+        For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``.
+
+        Raises ``dns.name.NoParent`` if the name is either the root name or the
+        empty name, and thus has no parent.
+
+        Returns a ``dns.name.Name``.
+        """
+
+        if self == root or self == empty:
+            raise NoParent
+        return Name(self.labels[1:])
+
+    def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
+        """Return the maximal predecessor of *name* in the DNSSEC ordering in the zone
+        whose origin is *origin*, or return the longest name under *origin* if the
+        name is origin (i.e. wrap around to the longest name, which may still be
+        *origin* due to length considerations.
+
+        The relativity of the name is preserved, so if this name is relative
+        then the method will return a relative name, and likewise if this name
+        is absolute then the predecessor will be absolute.
+
+        *prefix_ok* indicates if prefixing labels is allowed, and
+        defaults to ``True``.  Normally it is good to allow this, but if computing
+        a maximal predecessor at a zone cut point then ``False`` must be specified.
+        """
+        return _handle_relativity_and_call(
+            _absolute_predecessor, self, origin, prefix_ok
+        )
+
+    def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
+        """Return the minimal successor of *name* in the DNSSEC ordering in the zone
+        whose origin is *origin*, or return *origin* if the successor cannot be
+        computed due to name length limitations.
+
+        Note that *origin* is returned in the "too long" cases because wrapping
+        around to the origin is how NSEC records express "end of the zone".
+
+        The relativity of the name is preserved, so if this name is relative
+        then the method will return a relative name, and likewise if this name
+        is absolute then the successor will be absolute.
+
+        *prefix_ok* indicates if prefixing a new minimal label is allowed, and
+        defaults to ``True``.  Normally it is good to allow this, but if computing
+        a minimal successor at a zone cut point then ``False`` must be specified.
+        """
+        return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok)
+
+
+#: The root name, '.'
+root = Name([b""])
+
+#: The empty name.
+empty = Name([])
+
+
+def from_unicode(
+    text: str, origin: Optional[Name] = root, idna_codec: Optional[IDNACodec] = None
+) -> Name:
+    """Convert unicode text into a Name object.
+
+    Labels are encoded in IDN ACE form according to rules specified by
+    the IDNA codec.
+
+    *text*, a ``str``, is the text to convert into a name.
+
+    *origin*, a ``dns.name.Name``, specifies the origin to
+    append to non-absolute names.  The default is the root name.
+
+    *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
+    encoder/decoder.  If ``None``, the default IDNA 2003 encoder/decoder
+    is used.
+
+    Returns a ``dns.name.Name``.
+    """
+
+    if not isinstance(text, str):
+        raise ValueError("input to from_unicode() must be a unicode string")
+    if not (origin is None or isinstance(origin, Name)):
+        raise ValueError("origin must be a Name or None")
+    labels = []
+    label = ""
+    escaping = False
+    edigits = 0
+    total = 0
+    if idna_codec is None:
+        idna_codec = IDNA_2003
+    if text == "@":
+        text = ""
+    if text:
+        if text in [".", "\u3002", "\uff0e", "\uff61"]:
+            return Name([b""])  # no Unicode "u" on this constant!
+        for c in text:
+            if escaping:
+                if edigits == 0:
+                    if c.isdigit():
+                        total = int(c)
+                        edigits += 1
+                    else:
+                        label += c
+                        escaping = False
+                else:
+                    if not c.isdigit():
+                        raise BadEscape
+                    total *= 10
+                    total += int(c)
+                    edigits += 1
+                    if edigits == 3:
+                        escaping = False
+                        label += chr(total)
+            elif c in [".", "\u3002", "\uff0e", "\uff61"]:
+                if len(label) == 0:
+                    raise EmptyLabel
+                labels.append(idna_codec.encode(label))
+                label = ""
+            elif c == "\\":
+                escaping = True
+                edigits = 0
+                total = 0
+            else:
+                label += c
+        if escaping:
+            raise BadEscape
+        if len(label) > 0:
+            labels.append(idna_codec.encode(label))
+        else:
+            labels.append(b"")
+
+    if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
+        labels.extend(list(origin.labels))
+    return Name(labels)
+
+
+def is_all_ascii(text: str) -> bool:
+    for c in text:
+        if ord(c) > 0x7F:
+            return False
+    return True
+
+
+def from_text(
+    text: Union[bytes, str],
+    origin: Optional[Name] = root,
+    idna_codec: Optional[IDNACodec] = None,
+) -> Name:
+    """Convert text into a Name object.
+
+    *text*, a ``bytes`` or ``str``, is the text to convert into a name.
+
+    *origin*, a ``dns.name.Name``, specifies the origin to
+    append to non-absolute names.  The default is the root name.
+
+    *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
+    encoder/decoder.  If ``None``, the default IDNA 2003 encoder/decoder
+    is used.
+
+    Returns a ``dns.name.Name``.
+    """
+
+    if isinstance(text, str):
+        if not is_all_ascii(text):
+            # Some codepoint in the input text is > 127, so IDNA applies.
+            return from_unicode(text, origin, idna_codec)
+        # The input is all ASCII, so treat this like an ordinary non-IDNA
+        # domain name.  Note that "all ASCII" is about the input text,
+        # not the codepoints in the domain name.  E.g. if text has value
+        #
+        # r'\150\151\152\153\154\155\156\157\158\159'
+        #
+        # then it's still "all ASCII" even though the domain name has
+        # codepoints > 127.
+        text = text.encode("ascii")
+    if not isinstance(text, bytes):
+        raise ValueError("input to from_text() must be a string")
+    if not (origin is None or isinstance(origin, Name)):
+        raise ValueError("origin must be a Name or None")
+    labels = []
+    label = b""
+    escaping = False
+    edigits = 0
+    total = 0
+    if text == b"@":
+        text = b""
+    if text:
+        if text == b".":
+            return Name([b""])
+        for c in text:
+            byte_ = struct.pack("!B", c)
+            if escaping:
+                if edigits == 0:
+                    if byte_.isdigit():
+                        total = int(byte_)
+                        edigits += 1
+                    else:
+                        label += byte_
+                        escaping = False
+                else:
+                    if not byte_.isdigit():
+                        raise BadEscape
+                    total *= 10
+                    total += int(byte_)
+                    edigits += 1
+                    if edigits == 3:
+                        escaping = False
+                        label += struct.pack("!B", total)
+            elif byte_ == b".":
+                if len(label) == 0:
+                    raise EmptyLabel
+                labels.append(label)
+                label = b""
+            elif byte_ == b"\\":
+                escaping = True
+                edigits = 0
+                total = 0
+            else:
+                label += byte_
+        if escaping:
+            raise BadEscape
+        if len(label) > 0:
+            labels.append(label)
+        else:
+            labels.append(b"")
+    if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
+        labels.extend(list(origin.labels))
+    return Name(labels)
+
+
+# we need 'dns.wire.Parser' quoted as dns.name and dns.wire depend on each other.
+
+
+def from_wire_parser(parser: "dns.wire.Parser") -> Name:
+    """Convert possibly compressed wire format into a Name.
+
+    *parser* is a dns.wire.Parser.
+
+    Raises ``dns.name.BadPointer`` if a compression pointer did not
+    point backwards in the message.
+
+    Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
+
+    Returns a ``dns.name.Name``
+    """
+
+    labels = []
+    biggest_pointer = parser.current
+    with parser.restore_furthest():
+        count = parser.get_uint8()
+        while count != 0:
+            if count < 64:
+                labels.append(parser.get_bytes(count))
+            elif count >= 192:
+                current = (count & 0x3F) * 256 + parser.get_uint8()
+                if current >= biggest_pointer:
+                    raise BadPointer
+                biggest_pointer = current
+                parser.seek(current)
+            else:
+                raise BadLabelType
+            count = parser.get_uint8()
+        labels.append(b"")
+    return Name(labels)
+
+
+def from_wire(message: bytes, current: int) -> Tuple[Name, int]:
+    """Convert possibly compressed wire format into a Name.
+
+    *message* is a ``bytes`` containing an entire DNS message in DNS
+    wire form.
+
+    *current*, an ``int``, is the offset of the beginning of the name
+    from the start of the message
+
+    Raises ``dns.name.BadPointer`` if a compression pointer did not
+    point backwards in the message.
+
+    Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
+
+    Returns a ``(dns.name.Name, int)`` tuple consisting of the name
+    that was read and the number of bytes of the wire format message
+    which were consumed reading it.
+    """
+
+    if not isinstance(message, bytes):
+        raise ValueError("input to from_wire() must be a byte string")
+    parser = dns.wire.Parser(message, current)
+    name = from_wire_parser(parser)
+    return (name, parser.current - current)
+
+
+# RFC 4471 Support
+
+_MINIMAL_OCTET = b"\x00"
+_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET)
+_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET])
+_MAXIMAL_OCTET = b"\xff"
+_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET)
+_AT_SIGN_VALUE = ord("@")
+_LEFT_SQUARE_BRACKET_VALUE = ord("[")
+
+
+def _wire_length(labels):
+    return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0)
+
+
+def _pad_to_max_name(name):
+    needed = 255 - _wire_length(name.labels)
+    new_labels = []
+    while needed > 64:
+        new_labels.append(_MAXIMAL_OCTET * 63)
+        needed -= 64
+    if needed >= 2:
+        new_labels.append(_MAXIMAL_OCTET * (needed - 1))
+    # Note we're already maximal in the needed == 1 case as while we'd like
+    # to add one more byte as a new label, we can't, as adding a new non-empty
+    # label requires at least 2 bytes.
+    new_labels = list(reversed(new_labels))
+    new_labels.extend(name.labels)
+    return Name(new_labels)
+
+
+def _pad_to_max_label(label, suffix_labels):
+    length = len(label)
+    # We have to subtract one here to account for the length byte of label.
+    remaining = 255 - _wire_length(suffix_labels) - length - 1
+    if remaining <= 0:
+        # Shouldn't happen!
+        return label
+    needed = min(63 - length, remaining)
+    return label + _MAXIMAL_OCTET * needed
+
+
+def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name:
+    # This is the RFC 4471 predecessor algorithm using the "absolute method" of section
+    # 3.1.1.
+    #
+    # Our caller must ensure that the name and origin are absolute, and that name is a
+    # subdomain of origin.
+    if name == origin:
+        return _pad_to_max_name(name)
+    least_significant_label = name[0]
+    if least_significant_label == _MINIMAL_OCTET:
+        return name.parent()
+    least_octet = least_significant_label[-1]
+    suffix_labels = name.labels[1:]
+    if least_octet == _MINIMAL_OCTET_VALUE:
+        new_labels = [least_significant_label[:-1]]
+    else:
+        octets = bytearray(least_significant_label)
+        octet = octets[-1]
+        if octet == _LEFT_SQUARE_BRACKET_VALUE:
+            octet = _AT_SIGN_VALUE
+        else:
+            octet -= 1
+        octets[-1] = octet
+        least_significant_label = bytes(octets)
+        new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)]
+    new_labels.extend(suffix_labels)
+    name = Name(new_labels)
+    if prefix_ok:
+        return _pad_to_max_name(name)
+    else:
+        return name
+
+
+def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name:
+    # This is the RFC 4471 successor algorithm using the "absolute method" of section
+    # 3.1.2.
+    #
+    # Our caller must ensure that the name and origin are absolute, and that name is a
+    # subdomain of origin.
+    if prefix_ok:
+        # Try prefixing \000 as new label
+        try:
+            return _SUCCESSOR_PREFIX.concatenate(name)
+        except NameTooLong:
+            pass
+    while name != origin:
+        # Try extending the least significant label.
+        least_significant_label = name[0]
+        if len(least_significant_label) < 63:
+            # We may be able to extend the least label with a minimal additional byte.
+            # This is only "may" because we could have a maximal length name even though
+            # the least significant label isn't maximally long.
+            new_labels = [least_significant_label + _MINIMAL_OCTET]
+            new_labels.extend(name.labels[1:])
+            try:
+                return dns.name.Name(new_labels)
+            except dns.name.NameTooLong:
+                pass
+        # We can't extend the label either, so we'll try to increment the least
+        # signficant non-maximal byte in it.
+        octets = bytearray(least_significant_label)
+        # We do this reversed iteration with an explicit indexing variable because
+        # if we find something to increment, we're going to want to truncate everything
+        # to the right of it.
+        for i in range(len(octets) - 1, -1, -1):
+            octet = octets[i]
+            if octet == _MAXIMAL_OCTET_VALUE:
+                # We can't increment this, so keep looking.
+                continue
+            # Finally, something we can increment.  We have to apply a special rule for
+            # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when
+            # comparing names, uppercase letters compare as if they were their
+            # lower-case equivalents. If we increment "@" to "A", then it would compare
+            # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have
+            # skipped the most minimal successor, namely "[".
+            if octet == _AT_SIGN_VALUE:
+                octet = _LEFT_SQUARE_BRACKET_VALUE
+            else:
+                octet += 1
+            octets[i] = octet
+            # We can now truncate all of the maximal values we skipped (if any)
+            new_labels = [bytes(octets[: i + 1])]
+            new_labels.extend(name.labels[1:])
+            # We haven't changed the length of the name, so the Name constructor will
+            # always work.
+            return Name(new_labels)
+        # We couldn't increment, so chop off the least significant label and try
+        # again.
+        name = name.parent()
+
+    # We couldn't increment at all, so return the origin, as wrapping around is the
+    # DNSSEC way.
+    return origin
+
+
+def _handle_relativity_and_call(
+    function: Callable[[Name, Name, bool], Name],
+    name: Name,
+    origin: Name,
+    prefix_ok: bool,
+) -> Name:
+    # Make "name" absolute if needed, ensure that the origin is absolute,
+    # call function(), and then relativize the result if needed.
+    if not origin.is_absolute():
+        raise NeedAbsoluteNameOrOrigin
+    relative = not name.is_absolute()
+    if relative:
+        name = name.derelativize(origin)
+    elif not name.is_subdomain(origin):
+        raise NeedSubdomainOfOrigin
+    result_name = function(name, origin, prefix_ok)
+    if relative:
+        result_name = result_name.relativize(origin)
+    return result_name