codekingpro/portable-devtools
114k
1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license2 3# Copyright (C) 2001-2017 Nominum, Inc.4#5# Permission to use, copy, modify, and distribute this software and its6# documentation for any purpose with or without fee is hereby granted,7# provided that the above copyright notice and this permission notice8# appear in all copies.9#10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.17 18"""DNS Names.19"""20 21import copy22import encodings.idna # type: ignore23import functools24import struct25from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union26 27import dns._features28import dns.enum29import dns.exception30import dns.immutable31import dns.wire32 33if dns._features.have("idna"):34 import idna # type: ignore35 36 have_idna_2008 = True37else: # pragma: no cover38 have_idna_2008 = False39 40CompressType = Dict["Name", int]41 42 43class NameRelation(dns.enum.IntEnum):44 """Name relation result from fullcompare()."""45 46 # This is an IntEnum for backwards compatibility in case anyone47 # has hardwired the constants.48 49 #: The compared names have no relationship to each other.50 NONE = 051 #: the first name is a superdomain of the second.52 SUPERDOMAIN = 153 #: The first name is a subdomain of the second.54 SUBDOMAIN = 255 #: The compared names are equal.56 EQUAL = 357 #: The compared names have a common ancestor.58 COMMONANCESTOR = 459 60 @classmethod61 def _maximum(cls):62 return cls.COMMONANCESTOR63 64 @classmethod65 def _short_name(cls):66 return cls.__name__67 68 69# Backwards compatibility70NAMERELN_NONE = NameRelation.NONE71NAMERELN_SUPERDOMAIN = NameRelation.SUPERDOMAIN72NAMERELN_SUBDOMAIN = NameRelation.SUBDOMAIN73NAMERELN_EQUAL = NameRelation.EQUAL74NAMERELN_COMMONANCESTOR = NameRelation.COMMONANCESTOR75 76 77class EmptyLabel(dns.exception.SyntaxError):78 """A DNS label is empty."""79 80 81class BadEscape(dns.exception.SyntaxError):82 """An escaped code in a text format of DNS name is invalid."""83 84 85class BadPointer(dns.exception.FormError):86 """A DNS compression pointer points forward instead of backward."""87 88 89class BadLabelType(dns.exception.FormError):90 """The label type in DNS name wire format is unknown."""91 92 93class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):94 """An attempt was made to convert a non-absolute name to95 wire when there was also a non-absolute (or missing) origin."""96 97 98class NameTooLong(dns.exception.FormError):99 """A DNS name is > 255 octets long."""100 101 102class LabelTooLong(dns.exception.SyntaxError):103 """A DNS label is > 63 octets long."""104 105 106class AbsoluteConcatenation(dns.exception.DNSException):107 """An attempt was made to append anything other than the108 empty name to an absolute DNS name."""109 110 111class NoParent(dns.exception.DNSException):112 """An attempt was made to get the parent of the root name113 or the empty name."""114 115 116class NoIDNA2008(dns.exception.DNSException):117 """IDNA 2008 processing was requested but the idna module is not118 available."""119 120 121class IDNAException(dns.exception.DNSException):122 """IDNA processing raised an exception."""123 124 supp_kwargs = {"idna_exception"}125 fmt = "IDNA processing exception: {idna_exception}"126 127 # We do this as otherwise mypy complains about unexpected keyword argument128 # idna_exception129 def __init__(self, *args, **kwargs):130 super().__init__(*args, **kwargs)131 132 133class NeedSubdomainOfOrigin(dns.exception.DNSException):134 """An absolute name was provided that is not a subdomain of the specified origin."""135 136 137_escaped = b'"().;\\@$'138_escaped_text = '"().;\\@$'139 140 141def _escapify(label: Union[bytes, str]) -> str:142 """Escape the characters in label which need it.143 @returns: the escaped string144 @rtype: string"""145 if isinstance(label, bytes):146 # Ordinary DNS label mode. Escape special characters and values147 # < 0x20 or > 0x7f.148 text = ""149 for c in label:150 if c in _escaped:151 text += "\\" + chr(c)152 elif c > 0x20 and c < 0x7F:153 text += chr(c)154 else:155 text += "\\%03d" % c156 return text157 158 # Unicode label mode. Escape only special characters and values < 0x20159 text = ""160 for uc in label:161 if uc in _escaped_text:162 text += "\\" + uc163 elif uc <= "\x20":164 text += "\\%03d" % ord(uc)165 else:166 text += uc167 return text168 169 170class IDNACodec:171 """Abstract base class for IDNA encoder/decoders."""172 173 def __init__(self):174 pass175 176 def is_idna(self, label: bytes) -> bool:177 return label.lower().startswith(b"xn--")178 179 def encode(self, label: str) -> bytes:180 raise NotImplementedError # pragma: no cover181 182 def decode(self, label: bytes) -> str:183 # We do not apply any IDNA policy on decode.184 if self.is_idna(label):185 try:186 slabel = label[4:].decode("punycode")187 return _escapify(slabel)188 except Exception as e:189 raise IDNAException(idna_exception=e)190 else:191 return _escapify(label)192 193 194class IDNA2003Codec(IDNACodec):195 """IDNA 2003 encoder/decoder."""196 197 def __init__(self, strict_decode: bool = False):198 """Initialize the IDNA 2003 encoder/decoder.199 200 *strict_decode* is a ``bool``. If `True`, then IDNA2003 checking201 is done when decoding. This can cause failures if the name202 was encoded with IDNA2008. The default is `False`.203 """204 205 super().__init__()206 self.strict_decode = strict_decode207 208 def encode(self, label: str) -> bytes:209 """Encode *label*."""210 211 if label == "":212 return b""213 try:214 return encodings.idna.ToASCII(label)215 except UnicodeError:216 raise LabelTooLong217 218 def decode(self, label: bytes) -> str:219 """Decode *label*."""220 if not self.strict_decode:221 return super().decode(label)222 if label == b"":223 return ""224 try:225 return _escapify(encodings.idna.ToUnicode(label))226 except Exception as e:227 raise IDNAException(idna_exception=e)228 229 230class IDNA2008Codec(IDNACodec):231 """IDNA 2008 encoder/decoder."""232 233 def __init__(234 self,235 uts_46: bool = False,236 transitional: bool = False,237 allow_pure_ascii: bool = False,238 strict_decode: bool = False,239 ):240 """Initialize the IDNA 2008 encoder/decoder.241 242 *uts_46* is a ``bool``. If True, apply Unicode IDNA243 compatibility processing as described in Unicode Technical244 Standard #46 (https://unicode.org/reports/tr46/).245 If False, do not apply the mapping. The default is False.246 247 *transitional* is a ``bool``: If True, use the248 "transitional" mode described in Unicode Technical Standard249 #46. The default is False.250 251 *allow_pure_ascii* is a ``bool``. If True, then a label which252 consists of only ASCII characters is allowed. This is less253 strict than regular IDNA 2008, but is also necessary for mixed254 names, e.g. a name with starting with "_sip._tcp." and ending255 in an IDN suffix which would otherwise be disallowed. The256 default is False.257 258 *strict_decode* is a ``bool``: If True, then IDNA2008 checking259 is done when decoding. This can cause failures if the name260 was encoded with IDNA2003. The default is False.261 """262 super().__init__()263 self.uts_46 = uts_46264 self.transitional = transitional265 self.allow_pure_ascii = allow_pure_ascii266 self.strict_decode = strict_decode267 268 def encode(self, label: str) -> bytes:269 if label == "":270 return b""271 if self.allow_pure_ascii and is_all_ascii(label):272 encoded = label.encode("ascii")273 if len(encoded) > 63:274 raise LabelTooLong275 return encoded276 if not have_idna_2008:277 raise NoIDNA2008278 try:279 if self.uts_46:280 label = idna.uts46_remap(label, False, self.transitional)281 return idna.alabel(label)282 except idna.IDNAError as e:283 if e.args[0] == "Label too long":284 raise LabelTooLong285 else:286 raise IDNAException(idna_exception=e)287 288 def decode(self, label: bytes) -> str:289 if not self.strict_decode:290 return super().decode(label)291 if label == b"":292 return ""293 if not have_idna_2008:294 raise NoIDNA2008295 try:296 ulabel = idna.ulabel(label)297 if self.uts_46:298 ulabel = idna.uts46_remap(ulabel, False, self.transitional)299 return _escapify(ulabel)300 except (idna.IDNAError, UnicodeError) as e:301 raise IDNAException(idna_exception=e)302 303 304IDNA_2003_Practical = IDNA2003Codec(False)305IDNA_2003_Strict = IDNA2003Codec(True)306IDNA_2003 = IDNA_2003_Practical307IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)308IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)309IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)310IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)311IDNA_2008 = IDNA_2008_Practical312 313 314def _validate_labels(labels: Tuple[bytes, ...]) -> None:315 """Check for empty labels in the middle of a label sequence,316 labels that are too long, and for too many labels.317 318 Raises ``dns.name.NameTooLong`` if the name as a whole is too long.319 320 Raises ``dns.name.EmptyLabel`` if a label is empty (i.e. the root321 label) and appears in a position other than the end of the label322 sequence323 324 """325 326 l = len(labels)327 total = 0328 i = -1329 j = 0330 for label in labels:331 ll = len(label)332 total += ll + 1333 if ll > 63:334 raise LabelTooLong335 if i < 0 and label == b"":336 i = j337 j += 1338 if total > 255:339 raise NameTooLong340 if i >= 0 and i != l - 1:341 raise EmptyLabel342 343 344def _maybe_convert_to_binary(label: Union[bytes, str]) -> bytes:345 """If label is ``str``, convert it to ``bytes``. If it is already346 ``bytes`` just return it.347 348 """349 350 if isinstance(label, bytes):351 return label352 if isinstance(label, str):353 return label.encode()354 raise ValueError # pragma: no cover355 356 357@dns.immutable.immutable358class Name:359 """A DNS name.360 361 The dns.name.Name class represents a DNS name as a tuple of362 labels. Each label is a ``bytes`` in DNS wire format. Instances363 of the class are immutable.364 """365 366 __slots__ = ["labels"]367 368 def __init__(self, labels: Iterable[Union[bytes, str]]):369 """*labels* is any iterable whose values are ``str`` or ``bytes``."""370 371 blabels = [_maybe_convert_to_binary(x) for x in labels]372 self.labels = tuple(blabels)373 _validate_labels(self.labels)374 375 def __copy__(self):376 return Name(self.labels)377 378 def __deepcopy__(self, memo):379 return Name(copy.deepcopy(self.labels, memo))380 381 def __getstate__(self):382 # Names can be pickled383 return {"labels": self.labels}384 385 def __setstate__(self, state):386 super().__setattr__("labels", state["labels"])387 _validate_labels(self.labels)388 389 def is_absolute(self) -> bool:390 """Is the most significant label of this name the root label?391 392 Returns a ``bool``.393 """394 395 return len(self.labels) > 0 and self.labels[-1] == b""396 397 def is_wild(self) -> bool:398 """Is this name wild? (I.e. Is the least significant label '*'?)399 400 Returns a ``bool``.401 """402 403 return len(self.labels) > 0 and self.labels[0] == b"*"404 405 def __hash__(self) -> int:406 """Return a case-insensitive hash of the name.407 408 Returns an ``int``.409 """410 411 h = 0412 for label in self.labels:413 for c in label.lower():414 h += (h << 3) + c415 return h416 417 def fullcompare(self, other: "Name") -> Tuple[NameRelation, int, int]:418 """Compare two names, returning a 3-tuple419 ``(relation, order, nlabels)``.420 421 *relation* describes the relation ship between the names,422 and is one of: ``dns.name.NameRelation.NONE``,423 ``dns.name.NameRelation.SUPERDOMAIN``, ``dns.name.NameRelation.SUBDOMAIN``,424 ``dns.name.NameRelation.EQUAL``, or ``dns.name.NameRelation.COMMONANCESTOR``.425 426 *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==427 0 if *self* == *other*. A relative name is always less than an428 absolute name. If both names have the same relativity, then429 the DNSSEC order relation is used to order them.430 431 *nlabels* is the number of significant labels that the two names432 have in common.433 434 Here are some examples. Names ending in "." are absolute names,435 those not ending in "." are relative names.436 437 ============= ============= =========== ===== =======438 self other relation order nlabels439 ============= ============= =========== ===== =======440 www.example. www.example. equal 0 3441 www.example. example. subdomain > 0 2442 example. www.example. superdomain < 0 2443 example1.com. example2.com. common anc. < 0 2444 example1 example2. none < 0 0445 example1. example2 none > 0 0446 ============= ============= =========== ===== =======447 """448 449 sabs = self.is_absolute()450 oabs = other.is_absolute()451 if sabs != oabs:452 if sabs:453 return (NameRelation.NONE, 1, 0)454 else:455 return (NameRelation.NONE, -1, 0)456 l1 = len(self.labels)457 l2 = len(other.labels)458 ldiff = l1 - l2459 if ldiff < 0:460 l = l1461 else:462 l = l2463 464 order = 0465 nlabels = 0466 namereln = NameRelation.NONE467 while l > 0:468 l -= 1469 l1 -= 1470 l2 -= 1471 label1 = self.labels[l1].lower()472 label2 = other.labels[l2].lower()473 if label1 < label2:474 order = -1475 if nlabels > 0:476 namereln = NameRelation.COMMONANCESTOR477 return (namereln, order, nlabels)478 elif label1 > label2:479 order = 1480 if nlabels > 0:481 namereln = NameRelation.COMMONANCESTOR482 return (namereln, order, nlabels)483 nlabels += 1484 order = ldiff485 if ldiff < 0:486 namereln = NameRelation.SUPERDOMAIN487 elif ldiff > 0:488 namereln = NameRelation.SUBDOMAIN489 else:490 namereln = NameRelation.EQUAL491 return (namereln, order, nlabels)492 493 def is_subdomain(self, other: "Name") -> bool:494 """Is self a subdomain of other?495 496 Note that the notion of subdomain includes equality, e.g.497 "dnspython.org" is a subdomain of itself.498 499 Returns a ``bool``.500 """501 502 (nr, _, _) = self.fullcompare(other)503 if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL:504 return True505 return False506 507 def is_superdomain(self, other: "Name") -> bool:508 """Is self a superdomain of other?509 510 Note that the notion of superdomain includes equality, e.g.511 "dnspython.org" is a superdomain of itself.512 513 Returns a ``bool``.514 """515 516 (nr, _, _) = self.fullcompare(other)517 if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL:518 return True519 return False520 521 def canonicalize(self) -> "Name":522 """Return a name which is equal to the current name, but is in523 DNSSEC canonical form.524 """525 526 return Name([x.lower() for x in self.labels])527 528 def __eq__(self, other):529 if isinstance(other, Name):530 return self.fullcompare(other)[1] == 0531 else:532 return False533 534 def __ne__(self, other):535 if isinstance(other, Name):536 return self.fullcompare(other)[1] != 0537 else:538 return True539 540 def __lt__(self, other):541 if isinstance(other, Name):542 return self.fullcompare(other)[1] < 0543 else:544 return NotImplemented545 546 def __le__(self, other):547 if isinstance(other, Name):548 return self.fullcompare(other)[1] <= 0549 else:550 return NotImplemented551 552 def __ge__(self, other):553 if isinstance(other, Name):554 return self.fullcompare(other)[1] >= 0555 else:556 return NotImplemented557 558 def __gt__(self, other):559 if isinstance(other, Name):560 return self.fullcompare(other)[1] > 0561 else:562 return NotImplemented563 564 def __repr__(self):565 return "<DNS name " + self.__str__() + ">"566 567 def __str__(self):568 return self.to_text(False)569 570 def to_text(self, omit_final_dot: bool = False) -> str:571 """Convert name to DNS text format.572 573 *omit_final_dot* is a ``bool``. If True, don't emit the final574 dot (denoting the root label) for absolute names. The default575 is False.576 577 Returns a ``str``.578 """579 580 if len(self.labels) == 0:581 return "@"582 if len(self.labels) == 1 and self.labels[0] == b"":583 return "."584 if omit_final_dot and self.is_absolute():585 l = self.labels[:-1]586 else:587 l = self.labels588 s = ".".join(map(_escapify, l))589 return s590 591 def to_unicode(592 self, omit_final_dot: bool = False, idna_codec: Optional[IDNACodec] = None593 ) -> str:594 """Convert name to Unicode text format.595 596 IDN ACE labels are converted to Unicode.597 598 *omit_final_dot* is a ``bool``. If True, don't emit the final599 dot (denoting the root label) for absolute names. The default600 is False.601 *idna_codec* specifies the IDNA encoder/decoder. If None, the602 dns.name.IDNA_2003_Practical encoder/decoder is used.603 The IDNA_2003_Practical decoder does604 not impose any policy, it just decodes punycode, so if you605 don't want checking for compliance, you can use this decoder606 for IDNA2008 as well.607 608 Returns a ``str``.609 """610 611 if len(self.labels) == 0:612 return "@"613 if len(self.labels) == 1 and self.labels[0] == b"":614 return "."615 if omit_final_dot and self.is_absolute():616 l = self.labels[:-1]617 else:618 l = self.labels619 if idna_codec is None:620 idna_codec = IDNA_2003_Practical621 return ".".join([idna_codec.decode(x) for x in l])622 623 def to_digestable(self, origin: Optional["Name"] = None) -> bytes:624 """Convert name to a format suitable for digesting in hashes.625 626 The name is canonicalized and converted to uncompressed wire627 format. All names in wire format are absolute. If the name628 is a relative name, then an origin must be supplied.629 630 *origin* is a ``dns.name.Name`` or ``None``. If the name is631 relative and origin is not ``None``, then origin will be appended632 to the name.633 634 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is635 relative and no origin was provided.636 637 Returns a ``bytes``.638 """639 640 digest = self.to_wire(origin=origin, canonicalize=True)641 assert digest is not None642 return digest643 644 def to_wire(645 self,646 file: Optional[Any] = None,647 compress: Optional[CompressType] = None,648 origin: Optional["Name"] = None,649 canonicalize: bool = False,650 ) -> Optional[bytes]:651 """Convert name to wire format, possibly compressing it.652 653 *file* is the file where the name is emitted (typically an654 io.BytesIO file). If ``None`` (the default), a ``bytes``655 containing the wire name will be returned.656 657 *compress*, a ``dict``, is the compression table to use. If658 ``None`` (the default), names will not be compressed. Note that659 the compression code assumes that compression offset 0 is the660 start of *file*, and thus compression will not be correct661 if this is not the case.662 663 *origin* is a ``dns.name.Name`` or ``None``. If the name is664 relative and origin is not ``None``, then *origin* will be appended665 to it.666 667 *canonicalize*, a ``bool``, indicates whether the name should668 be canonicalized; that is, converted to a format suitable for669 digesting in hashes.670 671 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is672 relative and no origin was provided.673 674 Returns a ``bytes`` or ``None``.675 """676 677 if file is None:678 out = bytearray()679 for label in self.labels:680 out.append(len(label))681 if canonicalize:682 out += label.lower()683 else:684 out += label685 if not self.is_absolute():686 if origin is None or not origin.is_absolute():687 raise NeedAbsoluteNameOrOrigin688 for label in origin.labels:689 out.append(len(label))690 if canonicalize:691 out += label.lower()692 else:693 out += label694 return bytes(out)695 696 labels: Iterable[bytes]697 if not self.is_absolute():698 if origin is None or not origin.is_absolute():699 raise NeedAbsoluteNameOrOrigin700 labels = list(self.labels)701 labels.extend(list(origin.labels))702 else:703 labels = self.labels704 i = 0705 for label in labels:706 n = Name(labels[i:])707 i += 1708 if compress is not None:709 pos = compress.get(n)710 else:711 pos = None712 if pos is not None:713 value = 0xC000 + pos714 s = struct.pack("!H", value)715 file.write(s)716 break717 else:718 if compress is not None and len(n) > 1:719 pos = file.tell()720 if pos <= 0x3FFF:721 compress[n] = pos722 l = len(label)723 file.write(struct.pack("!B", l))724 if l > 0:725 if canonicalize:726 file.write(label.lower())727 else:728 file.write(label)729 return None730 731 def __len__(self) -> int:732 """The length of the name (in labels).733 734 Returns an ``int``.735 """736 737 return len(self.labels)738 739 def __getitem__(self, index):740 return self.labels[index]741 742 def __add__(self, other):743 return self.concatenate(other)744 745 def __sub__(self, other):746 return self.relativize(other)747 748 def split(self, depth: int) -> Tuple["Name", "Name"]:749 """Split a name into a prefix and suffix names at the specified depth.750 751 *depth* is an ``int`` specifying the number of labels in the suffix752 753 Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the754 name.755 756 Returns the tuple ``(prefix, suffix)``.757 """758 759 l = len(self.labels)760 if depth == 0:761 return (self, dns.name.empty)762 elif depth == l:763 return (dns.name.empty, self)764 elif depth < 0 or depth > l:765 raise ValueError("depth must be >= 0 and <= the length of the name")766 return (Name(self[:-depth]), Name(self[-depth:]))767 768 def concatenate(self, other: "Name") -> "Name":769 """Return a new name which is the concatenation of self and other.770 771 Raises ``dns.name.AbsoluteConcatenation`` if the name is772 absolute and *other* is not the empty name.773 774 Returns a ``dns.name.Name``.775 """776 777 if self.is_absolute() and len(other) > 0:778 raise AbsoluteConcatenation779 labels = list(self.labels)780 labels.extend(list(other.labels))781 return Name(labels)782 783 def relativize(self, origin: "Name") -> "Name":784 """If the name is a subdomain of *origin*, return a new name which is785 the name relative to origin. Otherwise return the name.786 787 For example, relativizing ``www.dnspython.org.`` to origin788 ``dnspython.org.`` returns the name ``www``. Relativizing ``example.``789 to origin ``dnspython.org.`` returns ``example.``.790 791 Returns a ``dns.name.Name``.792 """793 794 if origin is not None and self.is_subdomain(origin):795 return Name(self[: -len(origin)])796 else:797 return self798 799 def derelativize(self, origin: "Name") -> "Name":800 """If the name is a relative name, return a new name which is the801 concatenation of the name and origin. Otherwise return the name.802 803 For example, derelativizing ``www`` to origin ``dnspython.org.``804 returns the name ``www.dnspython.org.``. Derelativizing ``example.``805 to origin ``dnspython.org.`` returns ``example.``.806 807 Returns a ``dns.name.Name``.808 """809 810 if not self.is_absolute():811 return self.concatenate(origin)812 else:813 return self814 815 def choose_relativity(816 self, origin: Optional["Name"] = None, relativize: bool = True817 ) -> "Name":818 """Return a name with the relativity desired by the caller.819 820 If *origin* is ``None``, then the name is returned.821 Otherwise, if *relativize* is ``True`` the name is822 relativized, and if *relativize* is ``False`` the name is823 derelativized.824 825 Returns a ``dns.name.Name``.826 """827 828 if origin:829 if relativize:830 return self.relativize(origin)831 else:832 return self.derelativize(origin)833 else:834 return self835 836 def parent(self) -> "Name":837 """Return the parent of the name.838 839 For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``.840 841 Raises ``dns.name.NoParent`` if the name is either the root name or the842 empty name, and thus has no parent.843 844 Returns a ``dns.name.Name``.845 """846 847 if self == root or self == empty:848 raise NoParent849 return Name(self.labels[1:])850 851 def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name":852 """Return the maximal predecessor of *name* in the DNSSEC ordering in the zone853 whose origin is *origin*, or return the longest name under *origin* if the854 name is origin (i.e. wrap around to the longest name, which may still be855 *origin* due to length considerations.856 857 The relativity of the name is preserved, so if this name is relative858 then the method will return a relative name, and likewise if this name859 is absolute then the predecessor will be absolute.860 861 *prefix_ok* indicates if prefixing labels is allowed, and862 defaults to ``True``. Normally it is good to allow this, but if computing863 a maximal predecessor at a zone cut point then ``False`` must be specified.864 """865 return _handle_relativity_and_call(866 _absolute_predecessor, self, origin, prefix_ok867 )868 869 def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name":870 """Return the minimal successor of *name* in the DNSSEC ordering in the zone871 whose origin is *origin*, or return *origin* if the successor cannot be872 computed due to name length limitations.873 874 Note that *origin* is returned in the "too long" cases because wrapping875 around to the origin is how NSEC records express "end of the zone".876 877 The relativity of the name is preserved, so if this name is relative878 then the method will return a relative name, and likewise if this name879 is absolute then the successor will be absolute.880 881 *prefix_ok* indicates if prefixing a new minimal label is allowed, and882 defaults to ``True``. Normally it is good to allow this, but if computing883 a minimal successor at a zone cut point then ``False`` must be specified.884 """885 return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok)886 887 888#: The root name, '.'889root = Name([b""])890 891#: The empty name.892empty = Name([])893 894 895def from_unicode(896 text: str, origin: Optional[Name] = root, idna_codec: Optional[IDNACodec] = None897) -> Name:898 """Convert unicode text into a Name object.899 900 Labels are encoded in IDN ACE form according to rules specified by901 the IDNA codec.902 903 *text*, a ``str``, is the text to convert into a name.904 905 *origin*, a ``dns.name.Name``, specifies the origin to906 append to non-absolute names. The default is the root name.907 908 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA909 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder910 is used.911 912 Returns a ``dns.name.Name``.913 """914 915 if not isinstance(text, str):916 raise ValueError("input to from_unicode() must be a unicode string")917 if not (origin is None or isinstance(origin, Name)):918 raise ValueError("origin must be a Name or None")919 labels = []920 label = ""921 escaping = False922 edigits = 0923 total = 0924 if idna_codec is None:925 idna_codec = IDNA_2003926 if text == "@":927 text = ""928 if text:929 if text in [".", "\u3002", "\uff0e", "\uff61"]:930 return Name([b""]) # no Unicode "u" on this constant!931 for c in text:932 if escaping:933 if edigits == 0:934 if c.isdigit():935 total = int(c)936 edigits += 1937 else:938 label += c939 escaping = False940 else:941 if not c.isdigit():942 raise BadEscape943 total *= 10944 total += int(c)945 edigits += 1946 if edigits == 3:947 escaping = False948 label += chr(total)949 elif c in [".", "\u3002", "\uff0e", "\uff61"]:950 if len(label) == 0:951 raise EmptyLabel952 labels.append(idna_codec.encode(label))953 label = ""954 elif c == "\\":955 escaping = True956 edigits = 0957 total = 0958 else:959 label += c960 if escaping:961 raise BadEscape962 if len(label) > 0:963 labels.append(idna_codec.encode(label))964 else:965 labels.append(b"")966 967 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:968 labels.extend(list(origin.labels))969 return Name(labels)970 971 972def is_all_ascii(text: str) -> bool:973 for c in text:974 if ord(c) > 0x7F:975 return False976 return True977 978 979def from_text(980 text: Union[bytes, str],981 origin: Optional[Name] = root,982 idna_codec: Optional[IDNACodec] = None,983) -> Name:984 """Convert text into a Name object.985 986 *text*, a ``bytes`` or ``str``, is the text to convert into a name.987 988 *origin*, a ``dns.name.Name``, specifies the origin to989 append to non-absolute names. The default is the root name.990 991 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA992 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder993 is used.994 995 Returns a ``dns.name.Name``.996 """997 998 if isinstance(text, str):999 if not is_all_ascii(text):1000 # Some codepoint in the input text is > 127, so IDNA applies.1001 return from_unicode(text, origin, idna_codec)1002 # The input is all ASCII, so treat this like an ordinary non-IDNA1003 # domain name. Note that "all ASCII" is about the input text,1004 # not the codepoints in the domain name. E.g. if text has value1005 #1006 # r'\150\151\152\153\154\155\156\157\158\159'1007 #1008 # then it's still "all ASCII" even though the domain name has1009 # codepoints > 127.1010 text = text.encode("ascii")1011 if not isinstance(text, bytes):1012 raise ValueError("input to from_text() must be a string")1013 if not (origin is None or isinstance(origin, Name)):1014 raise ValueError("origin must be a Name or None")1015 labels = []1016 label = b""1017 escaping = False1018 edigits = 01019 total = 01020 if text == b"@":1021 text = b""1022 if text:1023 if text == b".":1024 return Name([b""])1025 for c in text:1026 byte_ = struct.pack("!B", c)1027 if escaping:1028 if edigits == 0:1029 if byte_.isdigit():1030 total = int(byte_)1031 edigits += 11032 else:1033 label += byte_1034 escaping = False1035 else:1036 if not byte_.isdigit():1037 raise BadEscape1038 total *= 101039 total += int(byte_)1040 edigits += 11041 if edigits == 3:1042 escaping = False1043 label += struct.pack("!B", total)1044 elif byte_ == b".":1045 if len(label) == 0:1046 raise EmptyLabel1047 labels.append(label)1048 label = b""1049 elif byte_ == b"\\":1050 escaping = True1051 edigits = 01052 total = 01053 else:1054 label += byte_1055 if escaping:1056 raise BadEscape1057 if len(label) > 0:1058 labels.append(label)1059 else:1060 labels.append(b"")1061 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:1062 labels.extend(list(origin.labels))1063 return Name(labels)1064 1065 1066# we need 'dns.wire.Parser' quoted as dns.name and dns.wire depend on each other.1067 1068 1069def from_wire_parser(parser: "dns.wire.Parser") -> Name:1070 """Convert possibly compressed wire format into a Name.1071 1072 *parser* is a dns.wire.Parser.1073 1074 Raises ``dns.name.BadPointer`` if a compression pointer did not1075 point backwards in the message.1076 1077 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.1078 1079 Returns a ``dns.name.Name``1080 """1081 1082 labels = []1083 biggest_pointer = parser.current1084 with parser.restore_furthest():1085 count = parser.get_uint8()1086 while count != 0:1087 if count < 64:1088 labels.append(parser.get_bytes(count))1089 elif count >= 192:1090 current = (count & 0x3F) * 256 + parser.get_uint8()1091 if current >= biggest_pointer:1092 raise BadPointer1093 biggest_pointer = current1094 parser.seek(current)1095 else:1096 raise BadLabelType1097 count = parser.get_uint8()1098 labels.append(b"")1099 return Name(labels)1100 1101 1102def from_wire(message: bytes, current: int) -> Tuple[Name, int]:1103 """Convert possibly compressed wire format into a Name.1104 1105 *message* is a ``bytes`` containing an entire DNS message in DNS1106 wire form.1107 1108 *current*, an ``int``, is the offset of the beginning of the name1109 from the start of the message1110 1111 Raises ``dns.name.BadPointer`` if a compression pointer did not1112 point backwards in the message.1113 1114 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.1115 1116 Returns a ``(dns.name.Name, int)`` tuple consisting of the name1117 that was read and the number of bytes of the wire format message1118 which were consumed reading it.1119 """1120 1121 if not isinstance(message, bytes):1122 raise ValueError("input to from_wire() must be a byte string")1123 parser = dns.wire.Parser(message, current)1124 name = from_wire_parser(parser)1125 return (name, parser.current - current)1126 1127 1128# RFC 4471 Support1129 1130_MINIMAL_OCTET = b"\x00"1131_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET)1132_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET])1133_MAXIMAL_OCTET = b"\xff"1134_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET)1135_AT_SIGN_VALUE = ord("@")1136_LEFT_SQUARE_BRACKET_VALUE = ord("[")1137 1138 1139def _wire_length(labels):1140 return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0)1141 1142 1143def _pad_to_max_name(name):1144 needed = 255 - _wire_length(name.labels)1145 new_labels = []1146 while needed > 64:1147 new_labels.append(_MAXIMAL_OCTET * 63)1148 needed -= 641149 if needed >= 2:1150 new_labels.append(_MAXIMAL_OCTET * (needed - 1))1151 # Note we're already maximal in the needed == 1 case as while we'd like1152 # to add one more byte as a new label, we can't, as adding a new non-empty1153 # label requires at least 2 bytes.1154 new_labels = list(reversed(new_labels))1155 new_labels.extend(name.labels)1156 return Name(new_labels)1157 1158 1159def _pad_to_max_label(label, suffix_labels):1160 length = len(label)1161 # We have to subtract one here to account for the length byte of label.1162 remaining = 255 - _wire_length(suffix_labels) - length - 11163 if remaining <= 0:1164 # Shouldn't happen!1165 return label1166 needed = min(63 - length, remaining)1167 return label + _MAXIMAL_OCTET * needed1168 1169 1170def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name:1171 # This is the RFC 4471 predecessor algorithm using the "absolute method" of section1172 # 3.1.1.1173 #1174 # Our caller must ensure that the name and origin are absolute, and that name is a1175 # subdomain of origin.1176 if name == origin:1177 return _pad_to_max_name(name)1178 least_significant_label = name[0]1179 if least_significant_label == _MINIMAL_OCTET:1180 return name.parent()1181 least_octet = least_significant_label[-1]1182 suffix_labels = name.labels[1:]1183 if least_octet == _MINIMAL_OCTET_VALUE:1184 new_labels = [least_significant_label[:-1]]1185 else:1186 octets = bytearray(least_significant_label)1187 octet = octets[-1]1188 if octet == _LEFT_SQUARE_BRACKET_VALUE:1189 octet = _AT_SIGN_VALUE1190 else:1191 octet -= 11192 octets[-1] = octet1193 least_significant_label = bytes(octets)1194 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)]1195 new_labels.extend(suffix_labels)1196 name = Name(new_labels)1197 if prefix_ok:1198 return _pad_to_max_name(name)1199 else:1200 return name