Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dns/name.py: 32%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
3# Copyright (C) 2001-2017 Nominum, Inc.
4#
5# Permission to use, copy, modify, and distribute this software and its
6# documentation for any purpose with or without fee is hereby granted,
7# provided that the above copyright notice and this permission notice
8# appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18"""DNS Names."""
20import copy
21import dataclasses
22import encodings.idna # pyright: ignore
23import functools
24import struct
25from collections.abc import Callable, Iterable
26from typing import Any
28import dns._features
29import dns._text_util
30import dns.enum
31import dns.exception
32import dns.immutable
33import dns.wirebase
34from dns.style import BaseStyle
36# Dnspython will never access idna if the import fails, but pyright can't figure
37# that out, so...
38#
39# pyright: reportAttributeAccessIssue = false, reportPossiblyUnboundVariable = false
41if dns._features.have("idna"):
42 import idna # pyright: ignore
44 have_idna_2008 = True
45else: # pragma: no cover
46 have_idna_2008 = False
49CompressType = dict["Name", int]
52class NameRelation(dns.enum.IntEnum):
53 """Name relation result from fullcompare()."""
55 # This is an IntEnum for backwards compatibility in case anyone
56 # has hardwired the constants.
58 #: The compared names have no relationship to each other.
59 NONE = 0
60 #: the first name is a superdomain of the second.
61 SUPERDOMAIN = 1
62 #: The first name is a subdomain of the second.
63 SUBDOMAIN = 2
64 #: The compared names are equal.
65 EQUAL = 3
66 #: The compared names have a common ancestor.
67 COMMONANCESTOR = 4
69 @classmethod
70 def _maximum(cls):
71 return cls.COMMONANCESTOR # pragma: no cover
73 @classmethod
74 def _short_name(cls):
75 return cls.__name__ # pragma: no cover
78# Backwards compatibility
79NAMERELN_NONE = NameRelation.NONE
80NAMERELN_SUPERDOMAIN = NameRelation.SUPERDOMAIN
81NAMERELN_SUBDOMAIN = NameRelation.SUBDOMAIN
82NAMERELN_EQUAL = NameRelation.EQUAL
83NAMERELN_COMMONANCESTOR = NameRelation.COMMONANCESTOR
86class EmptyLabel(dns.exception.SyntaxError):
87 """A DNS label is empty."""
90class BadEscape(dns.exception.SyntaxError):
91 """An escaped code in a text format of DNS name is invalid."""
94class BadPointer(dns.exception.FormError):
95 """A DNS compression pointer points forward instead of backward."""
98class PointerChainTooLong(dns.exception.FormError):
99 """A DNS compression pointer chain was too long."""
102class BadLabelType(dns.exception.FormError):
103 """The label type in DNS name wire format is unknown."""
106class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):
107 """An attempt was made to convert a non-absolute name to
108 wire when there was also a non-absolute (or missing) origin."""
111class NameTooLong(dns.exception.FormError):
112 """A DNS name is > 255 octets long."""
115class LabelTooLong(dns.exception.SyntaxError):
116 """A DNS label is > 63 octets long."""
119class AbsoluteConcatenation(dns.exception.DNSException):
120 """An attempt was made to append anything other than the
121 empty name to an absolute DNS name."""
124class NoParent(dns.exception.DNSException):
125 """An attempt was made to get the parent of the root name
126 or the empty name."""
129class NoIDNA2008(dns.exception.DNSException):
130 """IDNA 2008 processing was requested but the idna module is not
131 available."""
134class IDNAException(dns.exception.DNSException):
135 """IDNA processing raised an exception."""
137 supp_kwargs = {"idna_exception"}
138 fmt = "IDNA processing exception: {idna_exception}"
140 # We do this as otherwise mypy complains about unexpected keyword argument
141 # idna_exception
142 def __init__(self, *args, **kwargs):
143 super().__init__(*args, **kwargs)
146class NeedSubdomainOfOrigin(dns.exception.DNSException):
147 """An absolute name was provided that is not a subdomain of the specified origin."""
150_escaped = b'"().;\\@$'
151_escaped_text = '"().;\\@$'
154def _escapify(label: bytes | str) -> str:
155 """Escape the characters in label which need it.
157 :returns: the escaped string
158 :rtype: str
159 """
160 if isinstance(label, bytes):
161 # Ordinary DNS label mode. Escape special characters and values
162 # < 0x20 or > 0x7f.
163 text = ""
164 for c in label:
165 if c in _escaped:
166 text += "\\" + chr(c)
167 elif c > 0x20 and c < 0x7F:
168 text += chr(c)
169 else:
170 text += f"\\{c:03d}"
171 return text
173 # Unicode label mode. Escape only special characters and values < 0x20
174 text = ""
175 for uc in label:
176 if uc in _escaped_text:
177 text += "\\" + uc
178 elif uc <= "\x20":
179 text += f"\\{ord(uc):03d}"
180 else:
181 text += uc
182 return text
185class IDNACodec:
186 """Abstract base class for IDNA encoder/decoders."""
188 def __init__(self):
189 pass
191 def is_idna(self, label: bytes) -> bool:
192 return label.lower().startswith(b"xn--")
194 def encode(self, label: str) -> bytes:
195 raise NotImplementedError # pragma: no cover
197 def decode(self, label: bytes) -> str:
198 # We do not apply any IDNA policy on decode.
199 if self.is_idna(label):
200 try:
201 slabel = label[4:].decode("punycode")
202 return _escapify(slabel)
203 except Exception as e:
204 raise IDNAException(idna_exception=e)
205 else:
206 return _escapify(label)
209class IDNA2003Codec(IDNACodec):
210 """IDNA 2003 encoder/decoder."""
212 def __init__(self, strict_decode: bool = False):
213 """Initialize the IDNA 2003 encoder/decoder.
215 :param bool strict_decode: If ``True``, then IDNA2003 checking
216 is done when decoding. This can cause failures if the name
217 was encoded with IDNA2008. The default is ``False``.
218 """
220 super().__init__()
221 self.strict_decode = strict_decode
223 def encode(self, label: str) -> bytes:
224 """Encode *label*."""
226 if label == "":
227 return b""
228 try:
229 return encodings.idna.ToASCII(label)
230 except UnicodeError:
231 raise LabelTooLong
233 def decode(self, label: bytes) -> str:
234 """Decode *label*."""
235 if not self.strict_decode:
236 return super().decode(label)
237 if label == b"":
238 return ""
239 try:
240 return _escapify(encodings.idna.ToUnicode(label))
241 except Exception as e:
242 raise IDNAException(idna_exception=e)
245class IDNA2008Codec(IDNACodec):
246 """IDNA 2008 encoder/decoder."""
248 def __init__(
249 self,
250 uts_46: bool = False,
251 transitional: bool = False,
252 allow_pure_ascii: bool = False,
253 strict_decode: bool = False,
254 ):
255 """Initialize the IDNA 2008 encoder/decoder.
257 :param bool uts_46: If ``True``, apply Unicode IDNA compatibility
258 processing as described in Unicode Technical Standard #46
259 (https://unicode.org/reports/tr46/). If ``False``, do not
260 apply the mapping. The default is ``False``.
261 :param bool transitional: If ``True``, use the "transitional" mode
262 described in Unicode Technical Standard #46. The default is
263 ``False``. This setting has no effect in idna 3.11 and later
264 as transitional support has been removed.
265 :param bool allow_pure_ascii: If ``True``, then a label which
266 consists of only ASCII characters is allowed. This is less
267 strict than regular IDNA 2008, but is also necessary for mixed
268 names, e.g. a name starting with ``_sip._tcp.`` and ending
269 in an IDN suffix which would otherwise be disallowed. The
270 default is ``False``.
271 :param bool strict_decode: If ``True``, then IDNA2008 checking
272 is done when decoding. This can cause failures if the name
273 was encoded with IDNA2003. The default is ``False``.
274 """
275 super().__init__()
276 self.uts_46 = uts_46
277 self.transitional = transitional
278 self.allow_pure_ascii = allow_pure_ascii
279 self.strict_decode = strict_decode
281 def encode(self, label: str) -> bytes:
282 if label == "":
283 return b""
284 if self.allow_pure_ascii and is_all_ascii(label):
285 encoded = label.encode("ascii")
286 if len(encoded) > 63:
287 raise LabelTooLong
288 return encoded
289 if not have_idna_2008:
290 raise NoIDNA2008
291 try:
292 if self.uts_46:
293 # pylint: disable=possibly-used-before-assignment
294 label = idna.uts46_remap(label, False, self.transitional)
295 return idna.alabel(label)
296 except idna.IDNAError as e:
297 if e.args[0] == "Label too long":
298 raise LabelTooLong
299 else:
300 raise IDNAException(idna_exception=e)
302 def decode(self, label: bytes) -> str:
303 if not self.strict_decode:
304 return super().decode(label)
305 if label == b"":
306 return ""
307 if not have_idna_2008:
308 raise NoIDNA2008
309 try:
310 ulabel = idna.ulabel(label)
311 if self.uts_46:
312 ulabel = idna.uts46_remap(ulabel, False, self.transitional)
313 return _escapify(ulabel)
314 except (idna.IDNAError, UnicodeError) as e:
315 raise IDNAException(idna_exception=e)
318IDNA_2003_Practical = IDNA2003Codec(False)
319IDNA_2003_Strict = IDNA2003Codec(True)
320IDNA_2003 = IDNA_2003_Practical
321IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)
322IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)
323IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)
324IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)
325IDNA_2008 = IDNA_2008_Practical
326if have_idna_2008:
327 IDNA_DEFAULT = IDNA_2008_Practical
328else:
329 IDNA_DEFAULT = IDNA_2003_Practical
332def set_default_idna_codec(idna_codec: IDNACodec):
333 """Set the default IDNA codec."""
334 global IDNA_DEFAULT
335 IDNA_DEFAULT = idna_codec
338def _validate_labels(labels: tuple[bytes, ...]) -> None:
339 """Check for empty labels in the middle of a label sequence,
340 labels that are too long, and for too many labels.
342 :raises dns.name.NameTooLong: if the name as a whole is too long.
343 :raises dns.name.EmptyLabel: if a label is empty (i.e. the root
344 label) and appears in a position other than the end of the label
345 sequence.
346 """
348 l = len(labels)
349 total = 0
350 i = -1
351 j = 0
352 for label in labels:
353 ll = len(label)
354 total += ll + 1
355 if ll > 63:
356 raise LabelTooLong
357 if i < 0 and label == b"":
358 i = j
359 j += 1
360 if total > 255:
361 raise NameTooLong
362 if i >= 0 and i != l - 1:
363 raise EmptyLabel
366def _maybe_convert_to_binary(label: bytes | str) -> bytes:
367 """If label is ``str``, convert it to ``bytes``. If it is already
368 ``bytes`` just return it.
370 """
372 if isinstance(label, bytes):
373 return label
374 else:
375 return label.encode()
378@dataclasses.dataclass(frozen=True)
379class NameStyle(BaseStyle):
380 """Name text styles.
382 :param bool omit_final_dot: If ``True``, don't emit the final dot
383 (denoting the root label) for absolute names. The default is
384 ``False``.
385 :param idna_codec: The IDNA decoder to use. The default is ``None``,
386 which means all text is in the standard DNS zonefile format, i.e.
387 punycode will not be decoded.
388 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None``
389 :param origin: If ``None`` (the default), the name's relativity is not
390 altered before conversion to text. Otherwise, if *relativize* is
391 ``True`` the name is relativized, and if *relativize* is ``False``
392 the name is derelativized.
393 :type origin: :py:class:`dns.name.Name` or ``None``
394 :param bool relativize: Controls the direction of relativization when
395 *origin* is set.
396 """
398 omit_final_dot: bool = False
399 idna_codec: IDNACodec | None = None
400 origin: "Name | None" = None
401 relativize: bool = False
404@dns.immutable.immutable
405class Name:
406 """A DNS name.
408 The dns.name.Name class represents a DNS name as a tuple of
409 labels. Each label is a ``bytes`` in DNS wire format. Instances
410 of the class are immutable.
411 """
413 __slots__ = ["labels"]
415 def __init__(self, labels: Iterable[bytes | str]):
416 """Initialize a DNS name.
418 :param labels: An iterable whose values are ``str`` or ``bytes``.
419 """
421 blabels = [_maybe_convert_to_binary(x) for x in labels]
422 self.labels = tuple(blabels)
423 _validate_labels(self.labels)
425 def __copy__(self):
426 return Name(self.labels)
428 def __deepcopy__(self, memo):
429 return Name(copy.deepcopy(self.labels, memo))
431 def __getstate__(self):
432 # Names can be pickled
433 return {"labels": self.labels}
435 def __setstate__(self, state):
436 super().__setattr__("labels", state["labels"])
437 _validate_labels(self.labels)
439 def is_absolute(self) -> bool:
440 """Is the most significant label of this name the root label?
442 :rtype: bool
443 """
445 return len(self.labels) > 0 and self.labels[-1] == b""
447 def is_wild(self) -> bool:
448 """Is this name wild? (I.e. Is the least significant label ``'*'``?)
450 :rtype: bool
451 """
453 return len(self.labels) > 0 and self.labels[0] == b"*"
455 def __hash__(self) -> int:
456 """Return a case-insensitive hash of the name.
458 :rtype: int
459 """
461 h = 0
462 for label in self.labels:
463 for c in label.lower():
464 h += (h << 3) + c
465 return h
467 def fullcompare(self, other: "Name") -> tuple[NameRelation, int, int]:
468 """Compare two names, returning a 3-tuple
469 ``(relation, order, nlabels)``.
471 *relation* describes the relationship between the names, and is one
472 of: :py:attr:`dns.name.NameRelation.NONE`,
473 :py:attr:`dns.name.NameRelation.SUPERDOMAIN`,
474 :py:attr:`dns.name.NameRelation.SUBDOMAIN`,
475 :py:attr:`dns.name.NameRelation.EQUAL`, or
476 :py:attr:`dns.name.NameRelation.COMMONANCESTOR`.
478 *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==
479 0 if *self* == *other*. A relative name is always less than an
480 absolute name. If both names have the same relativity, then
481 the DNSSEC order relation is used to order them.
483 *nlabels* is the number of significant labels that the two names
484 have in common.
486 Here are some examples. Names ending in "." are absolute names,
487 those not ending in "." are relative names.
489 ============= ============= =========== ===== =======
490 self other relation order nlabels
491 ============= ============= =========== ===== =======
492 www.example. www.example. equal 0 3
493 www.example. example. subdomain > 0 2
494 example. www.example. superdomain < 0 2
495 example1.com. example2.com. common anc. < 0 2
496 example1 example2. none < 0 0
497 example1. example2 none > 0 0
498 ============= ============= =========== ===== =======
500 :param other: The name to compare with.
501 :type other: :py:class:`dns.name.Name`
502 :returns: A 3-tuple ``(relation, order, nlabels)``.
503 :rtype: tuple[:py:class:`dns.name.NameRelation`, int, int]
504 """
506 sabs = self.is_absolute()
507 oabs = other.is_absolute()
508 if sabs != oabs:
509 if sabs:
510 return (NameRelation.NONE, 1, 0)
511 else:
512 return (NameRelation.NONE, -1, 0)
513 l1 = len(self.labels)
514 l2 = len(other.labels)
515 ldiff = l1 - l2
516 if ldiff < 0:
517 l = l1
518 else:
519 l = l2
521 order = 0
522 nlabels = 0
523 namereln = NameRelation.NONE
524 while l > 0:
525 l -= 1
526 l1 -= 1
527 l2 -= 1
528 label1 = self.labels[l1].lower()
529 label2 = other.labels[l2].lower()
530 if label1 < label2:
531 order = -1
532 if nlabels > 0:
533 namereln = NameRelation.COMMONANCESTOR
534 return (namereln, order, nlabels)
535 elif label1 > label2:
536 order = 1
537 if nlabels > 0:
538 namereln = NameRelation.COMMONANCESTOR
539 return (namereln, order, nlabels)
540 nlabels += 1
541 order = ldiff
542 if ldiff < 0:
543 namereln = NameRelation.SUPERDOMAIN
544 elif ldiff > 0:
545 namereln = NameRelation.SUBDOMAIN
546 else:
547 namereln = NameRelation.EQUAL
548 return (namereln, order, nlabels)
550 def is_subdomain(self, other: "Name") -> bool:
551 """Is self a subdomain of other?
553 Note that the notion of subdomain includes equality, e.g.
554 ``dnspython.org`` is a subdomain of itself.
556 :rtype: bool
557 """
559 nr, _, _ = self.fullcompare(other)
560 if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL:
561 return True
562 return False
564 def is_superdomain(self, other: "Name") -> bool:
565 """Is self a superdomain of other?
567 Note that the notion of superdomain includes equality, e.g.
568 ``dnspython.org`` is a superdomain of itself.
570 :rtype: bool
571 """
573 nr, _, _ = self.fullcompare(other)
574 if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL:
575 return True
576 return False
578 def canonicalize(self) -> "Name":
579 """Return a name which is equal to the current name, but is in
580 DNSSEC canonical form.
582 :rtype: :py:class:`dns.name.Name`
583 """
585 return Name([x.lower() for x in self.labels])
587 def __eq__(self, other):
588 if isinstance(other, Name):
589 return self.fullcompare(other)[1] == 0
590 else:
591 return False
593 def __ne__(self, other):
594 if isinstance(other, Name):
595 return self.fullcompare(other)[1] != 0
596 else:
597 return True
599 def __lt__(self, other):
600 if isinstance(other, Name):
601 return self.fullcompare(other)[1] < 0
602 else:
603 return NotImplemented
605 def __le__(self, other):
606 if isinstance(other, Name):
607 return self.fullcompare(other)[1] <= 0
608 else:
609 return NotImplemented
611 def __ge__(self, other):
612 if isinstance(other, Name):
613 return self.fullcompare(other)[1] >= 0
614 else:
615 return NotImplemented
617 def __gt__(self, other):
618 if isinstance(other, Name):
619 return self.fullcompare(other)[1] > 0
620 else:
621 return NotImplemented
623 def __repr__(self):
624 return "<DNS name " + self.__str__() + ">"
626 def __str__(self):
627 return self.to_text(False)
629 def to_text(
630 self, omit_final_dot: bool = False, style: NameStyle | None = None
631 ) -> str:
632 """Convert name to DNS text format.
634 :param bool omit_final_dot: If ``True``, don't emit the final dot
635 (denoting the root label) for absolute names. The default is
636 ``False``.
637 :param style: If specified, the style overrides the other parameters.
638 :type style: :py:class:`dns.name.NameStyle` or ``None``
639 :rtype: str
640 """
641 if style is None:
642 style = NameStyle(omit_final_dot=omit_final_dot)
643 return self.to_styled_text(style)
645 def to_unicode(
646 self,
647 omit_final_dot: bool = False,
648 idna_codec: IDNACodec | None = None,
649 style: NameStyle | None = None,
650 ) -> str:
651 """Convert name to DNS text format.
653 IDN ACE labels are converted to Unicode using the specified codec.
655 :param bool omit_final_dot: If ``True``, don't emit the final dot
656 (denoting the root label) for absolute names. The default is
657 ``False``.
658 :param idna_codec: The IDNA codec to use for decoding ACE labels.
659 If ``None``, the default IDNA codec is used.
660 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None``
661 :param style: If specified, the style overrides the other parameters.
662 :type style: :py:class:`dns.name.NameStyle` or ``None``
663 :rtype: str
664 """
665 if idna_codec is None:
666 idna_codec = IDNA_DEFAULT
667 if style is None:
668 style = NameStyle(omit_final_dot=omit_final_dot, idna_codec=idna_codec)
669 return self.to_styled_text(style)
671 def to_styled_text(self, style: NameStyle) -> str:
672 """Convert name to text format, applying the style.
674 See the documentation for :py:class:`dns.name.NameStyle` for a description
675 of the style parameters.
677 :param style: The style to apply.
678 :type style: :py:class:`dns.name.NameStyle`
679 :rtype: str
680 """
682 name = self.choose_relativity(style.origin, style.relativize)
683 if len(name.labels) == 0:
684 return "@"
685 if len(name.labels) == 1 and name.labels[0] == b"":
686 return "."
687 if style.omit_final_dot and name.is_absolute():
688 l = name.labels[:-1]
689 else:
690 l = name.labels
691 if style.idna_codec is None:
692 return ".".join(map(_escapify, l))
693 else:
694 return ".".join([style.idna_codec.decode(x) for x in l])
696 def to_digestable(self, origin: "Name | None" = None) -> bytes:
697 """Convert name to a format suitable for digesting in hashes.
699 The name is canonicalized and converted to uncompressed wire
700 format. All names in wire format are absolute. If the name
701 is a relative name, then an origin must be supplied.
703 :param origin: If the name is relative and *origin* is not ``None``,
704 then *origin* will be appended to the name.
705 :type origin: :py:class:`dns.name.Name` or ``None``
706 :raises dns.name.NeedAbsoluteNameOrOrigin: if the name is relative
707 and no origin was provided.
708 :rtype: bytes
709 """
711 digest = self.to_wire(origin=origin, canonicalize=True)
712 assert digest is not None
713 return digest
715 def to_wire(
716 self,
717 file: Any | None = None,
718 compress: CompressType | None = None,
719 origin: "Name | None" = None,
720 canonicalize: bool = False,
721 ) -> bytes | None:
722 """Convert name to wire format, possibly compressing it.
724 :param file: The file where the name is emitted (typically an
725 ``io.BytesIO`` file). If ``None`` (the default), a ``bytes``
726 containing the wire name will be returned.
727 :param compress: The compression table to use. If ``None`` (the
728 default), names will not be compressed. Note that the compression
729 code assumes that compression offset 0 is the start of *file*,
730 and thus compression will not be correct if this is not the case.
731 :type compress: dict or ``None``
732 :param origin: If the name is relative and *origin* is not ``None``,
733 then *origin* will be appended to it.
734 :type origin: :py:class:`dns.name.Name` or ``None``
735 :param bool canonicalize: Whether the name should be canonicalized;
736 that is, converted to a format suitable for digesting in hashes.
737 :raises dns.name.NeedAbsoluteNameOrOrigin: if the name is relative
738 and no origin was provided.
739 :returns: ``None`` if *file* is provided, otherwise the wire format
740 as ``bytes``.
741 :rtype: bytes or ``None``
742 """
744 if file is None:
745 out = bytearray()
746 for label in self.labels:
747 out.append(len(label))
748 if canonicalize:
749 out += label.lower()
750 else:
751 out += label
752 if not self.is_absolute():
753 if origin is None or not origin.is_absolute():
754 raise NeedAbsoluteNameOrOrigin
755 for label in origin.labels:
756 out.append(len(label))
757 if canonicalize:
758 out += label.lower()
759 else:
760 out += label
761 return bytes(out)
763 labels: Iterable[bytes]
764 if not self.is_absolute():
765 if origin is None or not origin.is_absolute():
766 raise NeedAbsoluteNameOrOrigin
767 labels = list(self.labels)
768 labels.extend(list(origin.labels))
769 else:
770 labels = self.labels
771 i = 0
772 for label in labels:
773 n = Name(labels[i:])
774 i += 1
775 if compress is not None:
776 pos = compress.get(n)
777 else:
778 pos = None
779 if pos is not None:
780 value = 0xC000 + pos
781 s = struct.pack("!H", value)
782 file.write(s)
783 break
784 else:
785 if compress is not None and len(n) > 1:
786 pos = file.tell()
787 if pos <= 0x3FFF:
788 compress[n] = pos
789 l = len(label)
790 file.write(struct.pack("!B", l))
791 if l > 0:
792 if canonicalize:
793 file.write(label.lower())
794 else:
795 file.write(label)
796 return None
798 def __len__(self) -> int:
799 """The length of the name (in labels).
801 :rtype: int
802 """
804 return len(self.labels)
806 def __getitem__(self, index: Any) -> Any:
807 return self.labels[index]
809 def __add__(self, other):
810 return self.concatenate(other)
812 def __sub__(self, other):
813 return self.relativize(other)
815 def split(self, depth: int) -> tuple["Name", "Name"]:
816 """Split a name into a prefix and suffix names at the specified depth.
818 :param int depth: The number of labels in the suffix.
819 :raises ValueError: if *depth* is not >= 0 and <= the length of the
820 name.
821 :returns: A ``(prefix, suffix)`` tuple.
822 :rtype: tuple[:py:class:`dns.name.Name`, :py:class:`dns.name.Name`]
823 """
825 l = len(self.labels)
826 if depth == 0:
827 return (self, empty)
828 elif depth == l:
829 return (empty, self)
830 elif depth < 0 or depth > l:
831 raise ValueError("depth must be >= 0 and <= the length of the name")
832 return (Name(self[:-depth]), Name(self[-depth:]))
834 def concatenate(self, other: "Name") -> "Name":
835 """Return a new name which is the concatenation of self and other.
837 :param other: The name to concatenate.
838 :type other: :py:class:`dns.name.Name`
839 :raises dns.name.AbsoluteConcatenation: if the name is absolute and
840 *other* is not the empty name.
841 :rtype: :py:class:`dns.name.Name`
842 """
844 if self.is_absolute() and len(other) > 0:
845 raise AbsoluteConcatenation
846 labels = list(self.labels)
847 labels.extend(list(other.labels))
848 return Name(labels)
850 def relativize(self, origin: "Name") -> "Name":
851 """If the name is a subdomain of *origin*, return a new name which is
852 the name relative to origin. Otherwise return the name.
854 For example, relativizing ``www.dnspython.org.`` to origin
855 ``dnspython.org.`` returns the name ``www``. Relativizing ``example.``
856 to origin ``dnspython.org.`` returns ``example.``.
858 :param origin: The origin to relativize against.
859 :type origin: :py:class:`dns.name.Name`
860 :rtype: :py:class:`dns.name.Name`
861 """
863 if self.is_subdomain(origin):
864 return Name(self[: -len(origin)])
865 else:
866 return self
868 def derelativize(self, origin: "Name") -> "Name":
869 """If the name is a relative name, return a new name which is the
870 concatenation of the name and origin. Otherwise return the name.
872 For example, derelativizing ``www`` to origin ``dnspython.org.``
873 returns the name ``www.dnspython.org.``. Derelativizing ``example.``
874 to origin ``dnspython.org.`` returns ``example.``.
876 :param origin: The origin to append.
877 :type origin: :py:class:`dns.name.Name`
878 :rtype: :py:class:`dns.name.Name`
879 """
881 if not self.is_absolute():
882 return self.concatenate(origin)
883 else:
884 return self
886 def choose_relativity(
887 self, origin: "Name | None" = None, relativize: bool = True
888 ) -> "Name":
889 """Return a name with the relativity desired by the caller.
891 If *origin* is ``None``, then the name is returned.
892 Otherwise, if *relativize* is ``True`` the name is
893 relativized, and if *relativize* is ``False`` the name is
894 derelativized.
896 :param origin: If not ``None``, controls relativization.
897 :type origin: :py:class:`dns.name.Name` or ``None``
898 :param bool relativize: If ``True``, relativize; if ``False``,
899 derelativize.
900 :rtype: :py:class:`dns.name.Name`
901 """
903 if origin:
904 if relativize:
905 return self.relativize(origin)
906 else:
907 return self.derelativize(origin)
908 else:
909 return self
911 def parent(self) -> "Name":
912 """Return the parent of the name.
914 For example, the parent of ``www.dnspython.org.`` is ``dnspython.org.``.
916 :raises dns.name.NoParent: if the name is either the root name or the
917 empty name, and thus has no parent.
918 :rtype: :py:class:`dns.name.Name`
919 """
921 if self == root or self == empty:
922 raise NoParent
923 return Name(self.labels[1:])
925 def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
926 """Return the maximal predecessor of the name in the DNSSEC ordering.
928 Returns the maximal predecessor in the zone whose origin is *origin*,
929 or returns the longest name under *origin* if the name is *origin*
930 (i.e. wraps around to the longest name, which may still be *origin*
931 due to length considerations).
933 The relativity of the name is preserved, so if this name is relative
934 then the method will return a relative name, and likewise if this name
935 is absolute then the predecessor will be absolute.
937 :param origin: The zone origin.
938 :type origin: :py:class:`dns.name.Name`
939 :param bool prefix_ok: If ``True`` (the default), prefixing labels is
940 allowed. Specify ``False`` when computing a maximal predecessor
941 at a zone cut point.
942 :rtype: :py:class:`dns.name.Name`
943 """
944 return _handle_relativity_and_call(
945 _absolute_predecessor, self, origin, prefix_ok
946 )
948 def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
949 """Return the minimal successor of the name in the DNSSEC ordering.
951 Returns the minimal successor in the zone whose origin is *origin*,
952 or returns *origin* if the successor cannot be computed due to name
953 length limitations.
955 Note that *origin* is returned in the "too long" cases because wrapping
956 around to the origin is how NSEC records express "end of the zone".
958 The relativity of the name is preserved, so if this name is relative
959 then the method will return a relative name, and likewise if this name
960 is absolute then the successor will be absolute.
962 :param origin: The zone origin.
963 :type origin: :py:class:`dns.name.Name`
964 :param bool prefix_ok: If ``True`` (the default), prefixing a new
965 minimal label is allowed. Specify ``False`` when computing a
966 minimal successor at a zone cut point.
967 :rtype: :py:class:`dns.name.Name`
968 """
969 return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok)
972#: The root name, '.'
973root = Name([b""])
975#: The empty name.
976empty = Name([])
979def from_unicode(
980 text: str, origin: Name | None = root, idna_codec: IDNACodec | None = None
981) -> Name:
982 """Convert unicode text into a :py:class:`dns.name.Name` object.
984 Labels are encoded in IDN ACE form according to rules specified by
985 the IDNA codec.
987 :param str text: The text to convert into a name.
988 :param origin: The origin to append to non-absolute names. The
989 default is the root name.
990 :type origin: :py:class:`dns.name.Name` or ``None``
991 :param idna_codec: The IDNA encoder/decoder. If ``None``, the default
992 IDNA encoder/decoder is used.
993 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None``
994 :rtype: :py:class:`dns.name.Name`
995 """
997 labels = []
998 label = ""
999 escaping = False
1000 edigits = 0
1001 total = 0
1002 if idna_codec is None:
1003 idna_codec = IDNA_DEFAULT
1004 if text == "@":
1005 text = ""
1006 if text:
1007 if text in [".", "\u3002", "\uff0e", "\uff61"]:
1008 return Name([b""]) # no Unicode "u" on this constant!
1009 for c in text:
1010 if escaping:
1011 if edigits == 0:
1012 if dns._text_util.is_ascii_digit(c):
1013 total = int(c)
1014 edigits += 1
1015 else:
1016 label += c
1017 escaping = False
1018 else:
1019 if not dns._text_util.is_ascii_digit(c):
1020 raise BadEscape
1021 total *= 10
1022 total += int(c)
1023 edigits += 1
1024 if edigits == 3:
1025 if total > 255:
1026 raise BadEscape
1027 escaping = False
1028 label += chr(total)
1029 elif c in [".", "\u3002", "\uff0e", "\uff61"]:
1030 if len(label) == 0:
1031 raise EmptyLabel
1032 labels.append(idna_codec.encode(label))
1033 label = ""
1034 elif c == "\\":
1035 escaping = True
1036 edigits = 0
1037 total = 0
1038 else:
1039 label += c
1040 if escaping:
1041 raise BadEscape
1042 if len(label) > 0:
1043 labels.append(idna_codec.encode(label))
1044 else:
1045 labels.append(b"")
1047 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
1048 labels.extend(list(origin.labels))
1049 return Name(labels)
1052def is_all_ascii(text: str) -> bool:
1053 for c in text:
1054 if ord(c) > 0x7F:
1055 return False
1056 return True
1059def from_text(
1060 text: bytes | str,
1061 origin: Name | None = root,
1062 idna_codec: IDNACodec | None = None,
1063) -> Name:
1064 """Convert text into a :py:class:`dns.name.Name` object.
1066 :param text: The text to convert into a name.
1067 :type text: bytes or str
1068 :param origin: The origin to append to non-absolute names. The
1069 default is the root name.
1070 :type origin: :py:class:`dns.name.Name` or ``None``
1071 :param idna_codec: The IDNA encoder/decoder. If ``None``, the default
1072 IDNA encoder/decoder is used.
1073 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None``
1074 :rtype: :py:class:`dns.name.Name`
1075 """
1077 if isinstance(text, str):
1078 if not is_all_ascii(text):
1079 # Some codepoint in the input text is > 127, so IDNA applies.
1080 return from_unicode(text, origin, idna_codec)
1081 # The input is all ASCII, so treat this like an ordinary non-IDNA
1082 # domain name. Note that "all ASCII" is about the input text,
1083 # not the codepoints in the domain name. E.g. if text has value
1084 #
1085 # r'\150\151\152\153\154\155\156\157\158\159'
1086 #
1087 # then it's still "all ASCII" even though the domain name has
1088 # codepoints > 127.
1089 text = text.encode("ascii")
1090 labels = []
1091 label = b""
1092 escaping = False
1093 edigits = 0
1094 total = 0
1095 if text == b"@":
1096 text = b""
1097 if text:
1098 if text == b".":
1099 return Name([b""])
1100 for c in text:
1101 byte_ = struct.pack("!B", c)
1102 if escaping:
1103 if edigits == 0:
1104 if byte_.isdigit():
1105 total = int(byte_)
1106 edigits += 1
1107 else:
1108 label += byte_
1109 escaping = False
1110 else:
1111 if not byte_.isdigit():
1112 raise BadEscape
1113 total *= 10
1114 total += int(byte_)
1115 edigits += 1
1116 if edigits == 3:
1117 if total > 255:
1118 raise BadEscape
1119 escaping = False
1120 label += struct.pack("!B", total)
1121 elif byte_ == b".":
1122 if len(label) == 0:
1123 raise EmptyLabel
1124 labels.append(label)
1125 label = b""
1126 elif byte_ == b"\\":
1127 escaping = True
1128 edigits = 0
1129 total = 0
1130 else:
1131 label += byte_
1132 if escaping:
1133 raise BadEscape
1134 if len(label) > 0:
1135 labels.append(label)
1136 else:
1137 labels.append(b"")
1138 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
1139 labels.extend(list(origin.labels))
1140 return Name(labels)
1143MAX_COMPRESSION_POINTER_CHAIN = 16
1146def from_wire_parser(parser: dns.wirebase.Parser) -> Name:
1147 """Convert possibly compressed wire format into a :py:class:`dns.name.Name`.
1149 :param parser: The wire format parser.
1150 :type parser: :py:class:`dns.wirebase.Parser`
1151 :raises dns.name.BadPointer: if a compression pointer did not
1152 point backwards in the message.
1153 :raises dns.name.BadLabelType: if an invalid label type was encountered.
1154 :rtype: :py:class:`dns.name.Name`
1155 """
1157 labels = []
1158 biggest_pointer = parser.current
1159 hops = 0
1160 with parser.restore_furthest():
1161 count = parser.get_uint8()
1162 while count != 0:
1163 if count < 64:
1164 labels.append(parser.get_bytes(count))
1165 elif count >= 192:
1166 current = (count & 0x3F) * 256 + parser.get_uint8()
1167 if current >= biggest_pointer:
1168 raise BadPointer
1169 biggest_pointer = current
1170 hops += 1
1171 if hops > MAX_COMPRESSION_POINTER_CHAIN:
1172 raise PointerChainTooLong
1173 parser.seek(current)
1174 else:
1175 raise BadLabelType
1176 count = parser.get_uint8()
1177 labels.append(b"")
1178 return Name(labels)
1181def from_wire(message: bytes, current: int) -> tuple[Name, int]:
1182 """Convert possibly compressed wire format into a :py:class:`dns.name.Name`.
1184 :param bytes message: A ``bytes`` containing an entire DNS message in DNS
1185 wire form.
1186 :param int current: The offset of the beginning of the name from the
1187 start of the message.
1188 :raises dns.name.BadPointer: if a compression pointer did not
1189 point backwards in the message.
1190 :raises dns.name.BadLabelType: if an invalid label type was encountered.
1191 :returns: A tuple of the name that was read and the number of bytes of
1192 the wire format message which were consumed reading it.
1193 :rtype: tuple[:py:class:`dns.name.Name`, int]
1194 """
1196 parser = dns.wirebase.Parser(message, current)
1197 name = from_wire_parser(parser)
1198 return (name, parser.current - current)
1201# RFC 4471 Support
1203_MINIMAL_OCTET = b"\x00"
1204_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET)
1205_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET])
1206_MAXIMAL_OCTET = b"\xff"
1207_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET)
1208_AT_SIGN_VALUE = ord("@")
1209_LEFT_SQUARE_BRACKET_VALUE = ord("[")
1212def _wire_length(labels):
1213 return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0)
1216def _pad_to_max_name(name):
1217 needed = 255 - _wire_length(name.labels)
1218 new_labels = []
1219 while needed > 64:
1220 new_labels.append(_MAXIMAL_OCTET * 63)
1221 needed -= 64
1222 if needed >= 2:
1223 new_labels.append(_MAXIMAL_OCTET * (needed - 1))
1224 # Note we're already maximal in the needed == 1 case as while we'd like
1225 # to add one more byte as a new label, we can't, as adding a new non-empty
1226 # label requires at least 2 bytes.
1227 new_labels = list(reversed(new_labels))
1228 new_labels.extend(name.labels)
1229 return Name(new_labels)
1232def _pad_to_max_label(label, suffix_labels):
1233 length = len(label)
1234 # We have to subtract one here to account for the length byte of label.
1235 remaining = 255 - _wire_length(suffix_labels) - length - 1
1236 if remaining <= 0:
1237 # Shouldn't happen!
1238 return label
1239 needed = min(63 - length, remaining)
1240 return label + _MAXIMAL_OCTET * needed
1243def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1244 # This is the RFC 4471 predecessor algorithm using the "absolute method" of section
1245 # 3.1.1.
1246 #
1247 # Our caller must ensure that the name and origin are absolute, and that name is a
1248 # subdomain of origin.
1249 if name == origin:
1250 return _pad_to_max_name(name)
1251 least_significant_label = name[0]
1252 if least_significant_label == _MINIMAL_OCTET:
1253 return name.parent()
1254 least_octet = least_significant_label[-1]
1255 suffix_labels = name.labels[1:]
1256 if least_octet == _MINIMAL_OCTET_VALUE:
1257 new_labels = [least_significant_label[:-1]]
1258 else:
1259 octets = bytearray(least_significant_label)
1260 octet = octets[-1]
1261 if octet == _LEFT_SQUARE_BRACKET_VALUE:
1262 octet = _AT_SIGN_VALUE
1263 else:
1264 octet -= 1
1265 octets[-1] = octet
1266 least_significant_label = bytes(octets)
1267 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)]
1268 new_labels.extend(suffix_labels)
1269 name = Name(new_labels)
1270 if prefix_ok:
1271 return _pad_to_max_name(name)
1272 else:
1273 return name
1276def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1277 # This is the RFC 4471 successor algorithm using the "absolute method" of section
1278 # 3.1.2.
1279 #
1280 # Our caller must ensure that the name and origin are absolute, and that name is a
1281 # subdomain of origin.
1282 if prefix_ok:
1283 # Try prefixing \000 as new label
1284 try:
1285 return _SUCCESSOR_PREFIX.concatenate(name)
1286 except NameTooLong:
1287 pass
1288 while name != origin:
1289 # Try extending the least significant label.
1290 least_significant_label = name[0]
1291 if len(least_significant_label) < 63:
1292 # We may be able to extend the least label with a minimal additional byte.
1293 # This is only "may" because we could have a maximal length name even though
1294 # the least significant label isn't maximally long.
1295 new_labels = [least_significant_label + _MINIMAL_OCTET]
1296 new_labels.extend(name.labels[1:])
1297 try:
1298 return Name(new_labels)
1299 except NameTooLong:
1300 pass
1301 # We can't extend the label either, so we'll try to increment the least
1302 # signficant non-maximal byte in it.
1303 octets = bytearray(least_significant_label)
1304 # We do this reversed iteration with an explicit indexing variable because
1305 # if we find something to increment, we're going to want to truncate everything
1306 # to the right of it.
1307 for i in range(len(octets) - 1, -1, -1):
1308 octet = octets[i]
1309 if octet == _MAXIMAL_OCTET_VALUE:
1310 # We can't increment this, so keep looking.
1311 continue
1312 # Finally, something we can increment. We have to apply a special rule for
1313 # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when
1314 # comparing names, uppercase letters compare as if they were their
1315 # lower-case equivalents. If we increment "@" to "A", then it would compare
1316 # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have
1317 # skipped the most minimal successor, namely "[".
1318 if octet == _AT_SIGN_VALUE:
1319 octet = _LEFT_SQUARE_BRACKET_VALUE
1320 else:
1321 octet += 1
1322 octets[i] = octet
1323 # We can now truncate all of the maximal values we skipped (if any)
1324 new_labels = [bytes(octets[: i + 1])]
1325 new_labels.extend(name.labels[1:])
1326 # We haven't changed the length of the name, so the Name constructor will
1327 # always work.
1328 return Name(new_labels)
1329 # We couldn't increment, so chop off the least significant label and try
1330 # again.
1331 name = name.parent()
1333 # We couldn't increment at all, so return the origin, as wrapping around is the
1334 # DNSSEC way.
1335 return origin
1338def _handle_relativity_and_call(
1339 function: Callable[[Name, Name, bool], Name],
1340 name: Name,
1341 origin: Name,
1342 prefix_ok: bool,
1343) -> Name:
1344 # Make "name" absolute if needed, ensure that the origin is absolute,
1345 # call function(), and then relativize the result if needed.
1346 if not origin.is_absolute():
1347 raise NeedAbsoluteNameOrOrigin
1348 relative = not name.is_absolute()
1349 if relative:
1350 name = name.derelativize(origin)
1351 elif not name.is_subdomain(origin):
1352 raise NeedSubdomainOfOrigin
1353 result_name = function(name, origin, prefix_ok)
1354 if relative:
1355 result_name = result_name.relativize(origin)
1356 return result_name