Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dns/name.py: 27%
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.enum
30import dns.exception
31import dns.immutable
32import dns.wirebase
33from dns.style import BaseStyle
35# Dnspython will never access idna if the import fails, but pyright can't figure
36# that out, so...
37#
38# pyright: reportAttributeAccessIssue = false, reportPossiblyUnboundVariable = false
40if dns._features.have("idna"):
41 import idna # pyright: ignore
43 have_idna_2008 = True
44else: # pragma: no cover
45 have_idna_2008 = False
48CompressType = dict["Name", int]
51class NameRelation(dns.enum.IntEnum):
52 """Name relation result from fullcompare()."""
54 # This is an IntEnum for backwards compatibility in case anyone
55 # has hardwired the constants.
57 #: The compared names have no relationship to each other.
58 NONE = 0
59 #: the first name is a superdomain of the second.
60 SUPERDOMAIN = 1
61 #: The first name is a subdomain of the second.
62 SUBDOMAIN = 2
63 #: The compared names are equal.
64 EQUAL = 3
65 #: The compared names have a common ancestor.
66 COMMONANCESTOR = 4
68 @classmethod
69 def _maximum(cls):
70 return cls.COMMONANCESTOR # pragma: no cover
72 @classmethod
73 def _short_name(cls):
74 return cls.__name__ # pragma: no cover
77# Backwards compatibility
78NAMERELN_NONE = NameRelation.NONE
79NAMERELN_SUPERDOMAIN = NameRelation.SUPERDOMAIN
80NAMERELN_SUBDOMAIN = NameRelation.SUBDOMAIN
81NAMERELN_EQUAL = NameRelation.EQUAL
82NAMERELN_COMMONANCESTOR = NameRelation.COMMONANCESTOR
85class EmptyLabel(dns.exception.SyntaxError):
86 """A DNS label is empty."""
89class BadEscape(dns.exception.SyntaxError):
90 """An escaped code in a text format of DNS name is invalid."""
93class BadPointer(dns.exception.FormError):
94 """A DNS compression pointer points forward instead of backward."""
97class BadLabelType(dns.exception.FormError):
98 """The label type in DNS name wire format is unknown."""
101class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):
102 """An attempt was made to convert a non-absolute name to
103 wire when there was also a non-absolute (or missing) origin."""
106class NameTooLong(dns.exception.FormError):
107 """A DNS name is > 255 octets long."""
110class LabelTooLong(dns.exception.SyntaxError):
111 """A DNS label is > 63 octets long."""
114class AbsoluteConcatenation(dns.exception.DNSException):
115 """An attempt was made to append anything other than the
116 empty name to an absolute DNS name."""
119class NoParent(dns.exception.DNSException):
120 """An attempt was made to get the parent of the root name
121 or the empty name."""
124class NoIDNA2008(dns.exception.DNSException):
125 """IDNA 2008 processing was requested but the idna module is not
126 available."""
129class IDNAException(dns.exception.DNSException):
130 """IDNA processing raised an exception."""
132 supp_kwargs = {"idna_exception"}
133 fmt = "IDNA processing exception: {idna_exception}"
135 # We do this as otherwise mypy complains about unexpected keyword argument
136 # idna_exception
137 def __init__(self, *args, **kwargs):
138 super().__init__(*args, **kwargs)
141class NeedSubdomainOfOrigin(dns.exception.DNSException):
142 """An absolute name was provided that is not a subdomain of the specified origin."""
145_escaped = b'"().;\\@$'
146_escaped_text = '"().;\\@$'
149def _escapify(label: bytes | str) -> str:
150 """Escape the characters in label which need it.
152 :returns: the escaped string
153 :rtype: str
154 """
155 if isinstance(label, bytes):
156 # Ordinary DNS label mode. Escape special characters and values
157 # < 0x20 or > 0x7f.
158 text = ""
159 for c in label:
160 if c in _escaped:
161 text += "\\" + chr(c)
162 elif c > 0x20 and c < 0x7F:
163 text += chr(c)
164 else:
165 text += f"\\{c:03d}"
166 return text
168 # Unicode label mode. Escape only special characters and values < 0x20
169 text = ""
170 for uc in label:
171 if uc in _escaped_text:
172 text += "\\" + uc
173 elif uc <= "\x20":
174 text += f"\\{ord(uc):03d}"
175 else:
176 text += uc
177 return text
180class IDNACodec:
181 """Abstract base class for IDNA encoder/decoders."""
183 def __init__(self):
184 pass
186 def is_idna(self, label: bytes) -> bool:
187 return label.lower().startswith(b"xn--")
189 def encode(self, label: str) -> bytes:
190 raise NotImplementedError # pragma: no cover
192 def decode(self, label: bytes) -> str:
193 # We do not apply any IDNA policy on decode.
194 if self.is_idna(label):
195 try:
196 slabel = label[4:].decode("punycode")
197 return _escapify(slabel)
198 except Exception as e:
199 raise IDNAException(idna_exception=e)
200 else:
201 return _escapify(label)
204class IDNA2003Codec(IDNACodec):
205 """IDNA 2003 encoder/decoder."""
207 def __init__(self, strict_decode: bool = False):
208 """Initialize the IDNA 2003 encoder/decoder.
210 :param bool strict_decode: If ``True``, then IDNA2003 checking
211 is done when decoding. This can cause failures if the name
212 was encoded with IDNA2008. The default is ``False``.
213 """
215 super().__init__()
216 self.strict_decode = strict_decode
218 def encode(self, label: str) -> bytes:
219 """Encode *label*."""
221 if label == "":
222 return b""
223 try:
224 return encodings.idna.ToASCII(label)
225 except UnicodeError:
226 raise LabelTooLong
228 def decode(self, label: bytes) -> str:
229 """Decode *label*."""
230 if not self.strict_decode:
231 return super().decode(label)
232 if label == b"":
233 return ""
234 try:
235 return _escapify(encodings.idna.ToUnicode(label))
236 except Exception as e:
237 raise IDNAException(idna_exception=e)
240class IDNA2008Codec(IDNACodec):
241 """IDNA 2008 encoder/decoder."""
243 def __init__(
244 self,
245 uts_46: bool = False,
246 transitional: bool = False,
247 allow_pure_ascii: bool = False,
248 strict_decode: bool = False,
249 ):
250 """Initialize the IDNA 2008 encoder/decoder.
252 :param bool uts_46: If ``True``, apply Unicode IDNA compatibility
253 processing as described in Unicode Technical Standard #46
254 (https://unicode.org/reports/tr46/). If ``False``, do not
255 apply the mapping. The default is ``False``.
256 :param bool transitional: If ``True``, use the "transitional" mode
257 described in Unicode Technical Standard #46. The default is
258 ``False``. This setting has no effect in idna 3.11 and later
259 as transitional support has been removed.
260 :param bool allow_pure_ascii: If ``True``, then a label which
261 consists of only ASCII characters is allowed. This is less
262 strict than regular IDNA 2008, but is also necessary for mixed
263 names, e.g. a name starting with ``_sip._tcp.`` and ending
264 in an IDN suffix which would otherwise be disallowed. The
265 default is ``False``.
266 :param bool strict_decode: If ``True``, then IDNA2008 checking
267 is done when decoding. This can cause failures if the name
268 was encoded with IDNA2003. The default is ``False``.
269 """
270 super().__init__()
271 self.uts_46 = uts_46
272 self.transitional = transitional
273 self.allow_pure_ascii = allow_pure_ascii
274 self.strict_decode = strict_decode
276 def encode(self, label: str) -> bytes:
277 if label == "":
278 return b""
279 if self.allow_pure_ascii and is_all_ascii(label):
280 encoded = label.encode("ascii")
281 if len(encoded) > 63:
282 raise LabelTooLong
283 return encoded
284 if not have_idna_2008:
285 raise NoIDNA2008
286 try:
287 if self.uts_46:
288 # pylint: disable=possibly-used-before-assignment
289 label = idna.uts46_remap(label, False, self.transitional)
290 return idna.alabel(label)
291 except idna.IDNAError as e:
292 if e.args[0] == "Label too long":
293 raise LabelTooLong
294 else:
295 raise IDNAException(idna_exception=e)
297 def decode(self, label: bytes) -> str:
298 if not self.strict_decode:
299 return super().decode(label)
300 if label == b"":
301 return ""
302 if not have_idna_2008:
303 raise NoIDNA2008
304 try:
305 ulabel = idna.ulabel(label)
306 if self.uts_46:
307 ulabel = idna.uts46_remap(ulabel, False, self.transitional)
308 return _escapify(ulabel)
309 except (idna.IDNAError, UnicodeError) as e:
310 raise IDNAException(idna_exception=e)
313IDNA_2003_Practical = IDNA2003Codec(False)
314IDNA_2003_Strict = IDNA2003Codec(True)
315IDNA_2003 = IDNA_2003_Practical
316IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)
317IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)
318IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)
319IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)
320IDNA_2008 = IDNA_2008_Practical
321if have_idna_2008:
322 IDNA_DEFAULT = IDNA_2008_Practical
323else:
324 IDNA_DEFAULT = IDNA_2003_Practical
327def set_default_idna_codec(idna_codec: IDNACodec):
328 """Set the default IDNA codec."""
329 global IDNA_DEFAULT
330 IDNA_DEFAULT = idna_codec
333def _validate_labels(labels: tuple[bytes, ...]) -> None:
334 """Check for empty labels in the middle of a label sequence,
335 labels that are too long, and for too many labels.
337 :raises dns.name.NameTooLong: if the name as a whole is too long.
338 :raises dns.name.EmptyLabel: if a label is empty (i.e. the root
339 label) and appears in a position other than the end of the label
340 sequence.
341 """
343 l = len(labels)
344 total = 0
345 i = -1
346 j = 0
347 for label in labels:
348 ll = len(label)
349 total += ll + 1
350 if ll > 63:
351 raise LabelTooLong
352 if i < 0 and label == b"":
353 i = j
354 j += 1
355 if total > 255:
356 raise NameTooLong
357 if i >= 0 and i != l - 1:
358 raise EmptyLabel
361def _maybe_convert_to_binary(label: bytes | str) -> bytes:
362 """If label is ``str``, convert it to ``bytes``. If it is already
363 ``bytes`` just return it.
365 """
367 if isinstance(label, bytes):
368 return label
369 else:
370 return label.encode()
373@dataclasses.dataclass(frozen=True)
374class NameStyle(BaseStyle):
375 """Name text styles.
377 :param bool omit_final_dot: If ``True``, don't emit the final dot
378 (denoting the root label) for absolute names. The default is
379 ``False``.
380 :param idna_codec: The IDNA decoder to use. The default is ``None``,
381 which means all text is in the standard DNS zonefile format, i.e.
382 punycode will not be decoded.
383 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None``
384 :param origin: If ``None`` (the default), the name's relativity is not
385 altered before conversion to text. Otherwise, if *relativize* is
386 ``True`` the name is relativized, and if *relativize* is ``False``
387 the name is derelativized.
388 :type origin: :py:class:`dns.name.Name` or ``None``
389 :param bool relativize: Controls the direction of relativization when
390 *origin* is set.
391 """
393 omit_final_dot: bool = False
394 idna_codec: IDNACodec | None = None
395 origin: "Name | None" = None
396 relativize: bool = False
399@dns.immutable.immutable
400class Name:
401 """A DNS name.
403 The dns.name.Name class represents a DNS name as a tuple of
404 labels. Each label is a ``bytes`` in DNS wire format. Instances
405 of the class are immutable.
406 """
408 __slots__ = ["labels"]
410 def __init__(self, labels: Iterable[bytes | str]):
411 """Initialize a DNS name.
413 :param labels: An iterable whose values are ``str`` or ``bytes``.
414 """
416 blabels = [_maybe_convert_to_binary(x) for x in labels]
417 self.labels = tuple(blabels)
418 _validate_labels(self.labels)
420 def __copy__(self):
421 return Name(self.labels)
423 def __deepcopy__(self, memo):
424 return Name(copy.deepcopy(self.labels, memo))
426 def __getstate__(self):
427 # Names can be pickled
428 return {"labels": self.labels}
430 def __setstate__(self, state):
431 super().__setattr__("labels", state["labels"])
432 _validate_labels(self.labels)
434 def is_absolute(self) -> bool:
435 """Is the most significant label of this name the root label?
437 :rtype: bool
438 """
440 return len(self.labels) > 0 and self.labels[-1] == b""
442 def is_wild(self) -> bool:
443 """Is this name wild? (I.e. Is the least significant label ``'*'``?)
445 :rtype: bool
446 """
448 return len(self.labels) > 0 and self.labels[0] == b"*"
450 def __hash__(self) -> int:
451 """Return a case-insensitive hash of the name.
453 :rtype: int
454 """
456 h = 0
457 for label in self.labels:
458 for c in label.lower():
459 h += (h << 3) + c
460 return h
462 def fullcompare(self, other: "Name") -> tuple[NameRelation, int, int]:
463 """Compare two names, returning a 3-tuple
464 ``(relation, order, nlabels)``.
466 *relation* describes the relationship between the names, and is one
467 of: :py:attr:`dns.name.NameRelation.NONE`,
468 :py:attr:`dns.name.NameRelation.SUPERDOMAIN`,
469 :py:attr:`dns.name.NameRelation.SUBDOMAIN`,
470 :py:attr:`dns.name.NameRelation.EQUAL`, or
471 :py:attr:`dns.name.NameRelation.COMMONANCESTOR`.
473 *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==
474 0 if *self* == *other*. A relative name is always less than an
475 absolute name. If both names have the same relativity, then
476 the DNSSEC order relation is used to order them.
478 *nlabels* is the number of significant labels that the two names
479 have in common.
481 Here are some examples. Names ending in "." are absolute names,
482 those not ending in "." are relative names.
484 ============= ============= =========== ===== =======
485 self other relation order nlabels
486 ============= ============= =========== ===== =======
487 www.example. www.example. equal 0 3
488 www.example. example. subdomain > 0 2
489 example. www.example. superdomain < 0 2
490 example1.com. example2.com. common anc. < 0 2
491 example1 example2. none < 0 0
492 example1. example2 none > 0 0
493 ============= ============= =========== ===== =======
495 :param other: The name to compare with.
496 :type other: :py:class:`dns.name.Name`
497 :returns: A 3-tuple ``(relation, order, nlabels)``.
498 :rtype: tuple[:py:class:`dns.name.NameRelation`, int, int]
499 """
501 sabs = self.is_absolute()
502 oabs = other.is_absolute()
503 if sabs != oabs:
504 if sabs:
505 return (NameRelation.NONE, 1, 0)
506 else:
507 return (NameRelation.NONE, -1, 0)
508 l1 = len(self.labels)
509 l2 = len(other.labels)
510 ldiff = l1 - l2
511 if ldiff < 0:
512 l = l1
513 else:
514 l = l2
516 order = 0
517 nlabels = 0
518 namereln = NameRelation.NONE
519 while l > 0:
520 l -= 1
521 l1 -= 1
522 l2 -= 1
523 label1 = self.labels[l1].lower()
524 label2 = other.labels[l2].lower()
525 if label1 < label2:
526 order = -1
527 if nlabels > 0:
528 namereln = NameRelation.COMMONANCESTOR
529 return (namereln, order, nlabels)
530 elif label1 > label2:
531 order = 1
532 if nlabels > 0:
533 namereln = NameRelation.COMMONANCESTOR
534 return (namereln, order, nlabels)
535 nlabels += 1
536 order = ldiff
537 if ldiff < 0:
538 namereln = NameRelation.SUPERDOMAIN
539 elif ldiff > 0:
540 namereln = NameRelation.SUBDOMAIN
541 else:
542 namereln = NameRelation.EQUAL
543 return (namereln, order, nlabels)
545 def is_subdomain(self, other: "Name") -> bool:
546 """Is self a subdomain of other?
548 Note that the notion of subdomain includes equality, e.g.
549 ``dnspython.org`` is a subdomain of itself.
551 :rtype: bool
552 """
554 nr, _, _ = self.fullcompare(other)
555 if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL:
556 return True
557 return False
559 def is_superdomain(self, other: "Name") -> bool:
560 """Is self a superdomain of other?
562 Note that the notion of superdomain includes equality, e.g.
563 ``dnspython.org`` is a superdomain of itself.
565 :rtype: bool
566 """
568 nr, _, _ = self.fullcompare(other)
569 if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL:
570 return True
571 return False
573 def canonicalize(self) -> "Name":
574 """Return a name which is equal to the current name, but is in
575 DNSSEC canonical form.
577 :rtype: :py:class:`dns.name.Name`
578 """
580 return Name([x.lower() for x in self.labels])
582 def __eq__(self, other):
583 if isinstance(other, Name):
584 return self.fullcompare(other)[1] == 0
585 else:
586 return False
588 def __ne__(self, other):
589 if isinstance(other, Name):
590 return self.fullcompare(other)[1] != 0
591 else:
592 return True
594 def __lt__(self, other):
595 if isinstance(other, Name):
596 return self.fullcompare(other)[1] < 0
597 else:
598 return NotImplemented
600 def __le__(self, other):
601 if isinstance(other, Name):
602 return self.fullcompare(other)[1] <= 0
603 else:
604 return NotImplemented
606 def __ge__(self, other):
607 if isinstance(other, Name):
608 return self.fullcompare(other)[1] >= 0
609 else:
610 return NotImplemented
612 def __gt__(self, other):
613 if isinstance(other, Name):
614 return self.fullcompare(other)[1] > 0
615 else:
616 return NotImplemented
618 def __repr__(self):
619 return "<DNS name " + self.__str__() + ">"
621 def __str__(self):
622 return self.to_text(False)
624 def to_text(
625 self, omit_final_dot: bool = False, style: NameStyle | None = None
626 ) -> str:
627 """Convert name to DNS text format.
629 :param bool omit_final_dot: If ``True``, don't emit the final dot
630 (denoting the root label) for absolute names. The default is
631 ``False``.
632 :param style: If specified, the style overrides the other parameters.
633 :type style: :py:class:`dns.name.NameStyle` or ``None``
634 :rtype: str
635 """
636 if style is None:
637 style = NameStyle(omit_final_dot=omit_final_dot)
638 return self.to_styled_text(style)
640 def to_unicode(
641 self,
642 omit_final_dot: bool = False,
643 idna_codec: IDNACodec | None = None,
644 style: NameStyle | None = None,
645 ) -> str:
646 """Convert name to DNS text format.
648 IDN ACE labels are converted to Unicode using the specified codec.
650 :param bool omit_final_dot: If ``True``, don't emit the final dot
651 (denoting the root label) for absolute names. The default is
652 ``False``.
653 :param idna_codec: The IDNA codec to use for decoding ACE labels.
654 If ``None``, the default IDNA codec is used.
655 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None``
656 :param style: If specified, the style overrides the other parameters.
657 :type style: :py:class:`dns.name.NameStyle` or ``None``
658 :rtype: str
659 """
660 if idna_codec is None:
661 idna_codec = IDNA_DEFAULT
662 if style is None:
663 style = NameStyle(omit_final_dot=omit_final_dot, idna_codec=idna_codec)
664 return self.to_styled_text(style)
666 def to_styled_text(self, style: NameStyle) -> str:
667 """Convert name to text format, applying the style.
669 See the documentation for :py:class:`dns.name.NameStyle` for a description
670 of the style parameters.
672 :param style: The style to apply.
673 :type style: :py:class:`dns.name.NameStyle`
674 :rtype: str
675 """
677 name = self.choose_relativity(style.origin, style.relativize)
678 if len(name.labels) == 0:
679 return "@"
680 if len(name.labels) == 1 and name.labels[0] == b"":
681 return "."
682 if style.omit_final_dot and name.is_absolute():
683 l = name.labels[:-1]
684 else:
685 l = name.labels
686 if style.idna_codec is None:
687 return ".".join(map(_escapify, l))
688 else:
689 return ".".join([style.idna_codec.decode(x) for x in l])
691 def to_digestable(self, origin: "Name | None" = None) -> bytes:
692 """Convert name to a format suitable for digesting in hashes.
694 The name is canonicalized and converted to uncompressed wire
695 format. All names in wire format are absolute. If the name
696 is a relative name, then an origin must be supplied.
698 :param origin: If the name is relative and *origin* is not ``None``,
699 then *origin* will be appended to the name.
700 :type origin: :py:class:`dns.name.Name` or ``None``
701 :raises dns.name.NeedAbsoluteNameOrOrigin: if the name is relative
702 and no origin was provided.
703 :rtype: bytes
704 """
706 digest = self.to_wire(origin=origin, canonicalize=True)
707 assert digest is not None
708 return digest
710 def to_wire(
711 self,
712 file: Any | None = None,
713 compress: CompressType | None = None,
714 origin: "Name | None" = None,
715 canonicalize: bool = False,
716 ) -> bytes | None:
717 """Convert name to wire format, possibly compressing it.
719 :param file: The file where the name is emitted (typically an
720 ``io.BytesIO`` file). If ``None`` (the default), a ``bytes``
721 containing the wire name will be returned.
722 :param compress: The compression table to use. If ``None`` (the
723 default), names will not be compressed. Note that the compression
724 code assumes that compression offset 0 is the start of *file*,
725 and thus compression will not be correct if this is not the case.
726 :type compress: dict or ``None``
727 :param origin: If the name is relative and *origin* is not ``None``,
728 then *origin* will be appended to it.
729 :type origin: :py:class:`dns.name.Name` or ``None``
730 :param bool canonicalize: Whether the name should be canonicalized;
731 that is, converted to a format suitable for digesting in hashes.
732 :raises dns.name.NeedAbsoluteNameOrOrigin: if the name is relative
733 and no origin was provided.
734 :returns: ``None`` if *file* is provided, otherwise the wire format
735 as ``bytes``.
736 :rtype: bytes or ``None``
737 """
739 if file is None:
740 out = bytearray()
741 for label in self.labels:
742 out.append(len(label))
743 if canonicalize:
744 out += label.lower()
745 else:
746 out += label
747 if not self.is_absolute():
748 if origin is None or not origin.is_absolute():
749 raise NeedAbsoluteNameOrOrigin
750 for label in origin.labels:
751 out.append(len(label))
752 if canonicalize:
753 out += label.lower()
754 else:
755 out += label
756 return bytes(out)
758 labels: Iterable[bytes]
759 if not self.is_absolute():
760 if origin is None or not origin.is_absolute():
761 raise NeedAbsoluteNameOrOrigin
762 labels = list(self.labels)
763 labels.extend(list(origin.labels))
764 else:
765 labels = self.labels
766 i = 0
767 for label in labels:
768 n = Name(labels[i:])
769 i += 1
770 if compress is not None:
771 pos = compress.get(n)
772 else:
773 pos = None
774 if pos is not None:
775 value = 0xC000 + pos
776 s = struct.pack("!H", value)
777 file.write(s)
778 break
779 else:
780 if compress is not None and len(n) > 1:
781 pos = file.tell()
782 if pos <= 0x3FFF:
783 compress[n] = pos
784 l = len(label)
785 file.write(struct.pack("!B", l))
786 if l > 0:
787 if canonicalize:
788 file.write(label.lower())
789 else:
790 file.write(label)
791 return None
793 def __len__(self) -> int:
794 """The length of the name (in labels).
796 :rtype: int
797 """
799 return len(self.labels)
801 def __getitem__(self, index: Any) -> Any:
802 return self.labels[index]
804 def __add__(self, other):
805 return self.concatenate(other)
807 def __sub__(self, other):
808 return self.relativize(other)
810 def split(self, depth: int) -> tuple["Name", "Name"]:
811 """Split a name into a prefix and suffix names at the specified depth.
813 :param int depth: The number of labels in the suffix.
814 :raises ValueError: if *depth* is not >= 0 and <= the length of the
815 name.
816 :returns: A ``(prefix, suffix)`` tuple.
817 :rtype: tuple[:py:class:`dns.name.Name`, :py:class:`dns.name.Name`]
818 """
820 l = len(self.labels)
821 if depth == 0:
822 return (self, empty)
823 elif depth == l:
824 return (empty, self)
825 elif depth < 0 or depth > l:
826 raise ValueError("depth must be >= 0 and <= the length of the name")
827 return (Name(self[:-depth]), Name(self[-depth:]))
829 def concatenate(self, other: "Name") -> "Name":
830 """Return a new name which is the concatenation of self and other.
832 :param other: The name to concatenate.
833 :type other: :py:class:`dns.name.Name`
834 :raises dns.name.AbsoluteConcatenation: if the name is absolute and
835 *other* is not the empty name.
836 :rtype: :py:class:`dns.name.Name`
837 """
839 if self.is_absolute() and len(other) > 0:
840 raise AbsoluteConcatenation
841 labels = list(self.labels)
842 labels.extend(list(other.labels))
843 return Name(labels)
845 def relativize(self, origin: "Name") -> "Name":
846 """If the name is a subdomain of *origin*, return a new name which is
847 the name relative to origin. Otherwise return the name.
849 For example, relativizing ``www.dnspython.org.`` to origin
850 ``dnspython.org.`` returns the name ``www``. Relativizing ``example.``
851 to origin ``dnspython.org.`` returns ``example.``.
853 :param origin: The origin to relativize against.
854 :type origin: :py:class:`dns.name.Name`
855 :rtype: :py:class:`dns.name.Name`
856 """
858 if self.is_subdomain(origin):
859 return Name(self[: -len(origin)])
860 else:
861 return self
863 def derelativize(self, origin: "Name") -> "Name":
864 """If the name is a relative name, return a new name which is the
865 concatenation of the name and origin. Otherwise return the name.
867 For example, derelativizing ``www`` to origin ``dnspython.org.``
868 returns the name ``www.dnspython.org.``. Derelativizing ``example.``
869 to origin ``dnspython.org.`` returns ``example.``.
871 :param origin: The origin to append.
872 :type origin: :py:class:`dns.name.Name`
873 :rtype: :py:class:`dns.name.Name`
874 """
876 if not self.is_absolute():
877 return self.concatenate(origin)
878 else:
879 return self
881 def choose_relativity(
882 self, origin: "Name | None" = None, relativize: bool = True
883 ) -> "Name":
884 """Return a name with the relativity desired by the caller.
886 If *origin* is ``None``, then the name is returned.
887 Otherwise, if *relativize* is ``True`` the name is
888 relativized, and if *relativize* is ``False`` the name is
889 derelativized.
891 :param origin: If not ``None``, controls relativization.
892 :type origin: :py:class:`dns.name.Name` or ``None``
893 :param bool relativize: If ``True``, relativize; if ``False``,
894 derelativize.
895 :rtype: :py:class:`dns.name.Name`
896 """
898 if origin:
899 if relativize:
900 return self.relativize(origin)
901 else:
902 return self.derelativize(origin)
903 else:
904 return self
906 def parent(self) -> "Name":
907 """Return the parent of the name.
909 For example, the parent of ``www.dnspython.org.`` is ``dnspython.org.``.
911 :raises dns.name.NoParent: if the name is either the root name or the
912 empty name, and thus has no parent.
913 :rtype: :py:class:`dns.name.Name`
914 """
916 if self == root or self == empty:
917 raise NoParent
918 return Name(self.labels[1:])
920 def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
921 """Return the maximal predecessor of the name in the DNSSEC ordering.
923 Returns the maximal predecessor in the zone whose origin is *origin*,
924 or returns the longest name under *origin* if the name is *origin*
925 (i.e. wraps around to the longest name, which may still be *origin*
926 due to length considerations).
928 The relativity of the name is preserved, so if this name is relative
929 then the method will return a relative name, and likewise if this name
930 is absolute then the predecessor will be absolute.
932 :param origin: The zone origin.
933 :type origin: :py:class:`dns.name.Name`
934 :param bool prefix_ok: If ``True`` (the default), prefixing labels is
935 allowed. Specify ``False`` when computing a maximal predecessor
936 at a zone cut point.
937 :rtype: :py:class:`dns.name.Name`
938 """
939 return _handle_relativity_and_call(
940 _absolute_predecessor, self, origin, prefix_ok
941 )
943 def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
944 """Return the minimal successor of the name in the DNSSEC ordering.
946 Returns the minimal successor in the zone whose origin is *origin*,
947 or returns *origin* if the successor cannot be computed due to name
948 length limitations.
950 Note that *origin* is returned in the "too long" cases because wrapping
951 around to the origin is how NSEC records express "end of the zone".
953 The relativity of the name is preserved, so if this name is relative
954 then the method will return a relative name, and likewise if this name
955 is absolute then the successor will be absolute.
957 :param origin: The zone origin.
958 :type origin: :py:class:`dns.name.Name`
959 :param bool prefix_ok: If ``True`` (the default), prefixing a new
960 minimal label is allowed. Specify ``False`` when computing a
961 minimal successor at a zone cut point.
962 :rtype: :py:class:`dns.name.Name`
963 """
964 return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok)
967#: The root name, '.'
968root = Name([b""])
970#: The empty name.
971empty = Name([])
974def from_unicode(
975 text: str, origin: Name | None = root, idna_codec: IDNACodec | None = None
976) -> Name:
977 """Convert unicode text into a :py:class:`dns.name.Name` object.
979 Labels are encoded in IDN ACE form according to rules specified by
980 the IDNA codec.
982 :param str text: The text to convert into a name.
983 :param origin: The origin to append to non-absolute names. The
984 default is the root name.
985 :type origin: :py:class:`dns.name.Name` or ``None``
986 :param idna_codec: The IDNA encoder/decoder. If ``None``, the default
987 IDNA encoder/decoder is used.
988 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None``
989 :rtype: :py:class:`dns.name.Name`
990 """
992 labels = []
993 label = ""
994 escaping = False
995 edigits = 0
996 total = 0
997 if idna_codec is None:
998 idna_codec = IDNA_DEFAULT
999 if text == "@":
1000 text = ""
1001 if text:
1002 if text in [".", "\u3002", "\uff0e", "\uff61"]:
1003 return Name([b""]) # no Unicode "u" on this constant!
1004 for c in text:
1005 if escaping:
1006 if edigits == 0:
1007 if c.isdigit():
1008 total = int(c)
1009 edigits += 1
1010 else:
1011 label += c
1012 escaping = False
1013 else:
1014 if not c.isdigit():
1015 raise BadEscape
1016 total *= 10
1017 total += int(c)
1018 edigits += 1
1019 if edigits == 3:
1020 escaping = False
1021 label += chr(total)
1022 elif c in [".", "\u3002", "\uff0e", "\uff61"]:
1023 if len(label) == 0:
1024 raise EmptyLabel
1025 labels.append(idna_codec.encode(label))
1026 label = ""
1027 elif c == "\\":
1028 escaping = True
1029 edigits = 0
1030 total = 0
1031 else:
1032 label += c
1033 if escaping:
1034 raise BadEscape
1035 if len(label) > 0:
1036 labels.append(idna_codec.encode(label))
1037 else:
1038 labels.append(b"")
1040 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
1041 labels.extend(list(origin.labels))
1042 return Name(labels)
1045def is_all_ascii(text: str) -> bool:
1046 for c in text:
1047 if ord(c) > 0x7F:
1048 return False
1049 return True
1052def from_text(
1053 text: bytes | str,
1054 origin: Name | None = root,
1055 idna_codec: IDNACodec | None = None,
1056) -> Name:
1057 """Convert text into a :py:class:`dns.name.Name` object.
1059 :param text: The text to convert into a name.
1060 :type text: bytes or str
1061 :param origin: The origin to append to non-absolute names. The
1062 default is the root name.
1063 :type origin: :py:class:`dns.name.Name` or ``None``
1064 :param idna_codec: The IDNA encoder/decoder. If ``None``, the default
1065 IDNA encoder/decoder is used.
1066 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None``
1067 :rtype: :py:class:`dns.name.Name`
1068 """
1070 if isinstance(text, str):
1071 if not is_all_ascii(text):
1072 # Some codepoint in the input text is > 127, so IDNA applies.
1073 return from_unicode(text, origin, idna_codec)
1074 # The input is all ASCII, so treat this like an ordinary non-IDNA
1075 # domain name. Note that "all ASCII" is about the input text,
1076 # not the codepoints in the domain name. E.g. if text has value
1077 #
1078 # r'\150\151\152\153\154\155\156\157\158\159'
1079 #
1080 # then it's still "all ASCII" even though the domain name has
1081 # codepoints > 127.
1082 text = text.encode("ascii")
1083 labels = []
1084 label = b""
1085 escaping = False
1086 edigits = 0
1087 total = 0
1088 if text == b"@":
1089 text = b""
1090 if text:
1091 if text == b".":
1092 return Name([b""])
1093 for c in text:
1094 byte_ = struct.pack("!B", c)
1095 if escaping:
1096 if edigits == 0:
1097 if byte_.isdigit():
1098 total = int(byte_)
1099 edigits += 1
1100 else:
1101 label += byte_
1102 escaping = False
1103 else:
1104 if not byte_.isdigit():
1105 raise BadEscape
1106 total *= 10
1107 total += int(byte_)
1108 edigits += 1
1109 if edigits == 3:
1110 escaping = False
1111 label += struct.pack("!B", total)
1112 elif byte_ == b".":
1113 if len(label) == 0:
1114 raise EmptyLabel
1115 labels.append(label)
1116 label = b""
1117 elif byte_ == b"\\":
1118 escaping = True
1119 edigits = 0
1120 total = 0
1121 else:
1122 label += byte_
1123 if escaping:
1124 raise BadEscape
1125 if len(label) > 0:
1126 labels.append(label)
1127 else:
1128 labels.append(b"")
1129 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
1130 labels.extend(list(origin.labels))
1131 return Name(labels)
1134def from_wire_parser(parser: dns.wirebase.Parser) -> Name:
1135 """Convert possibly compressed wire format into a :py:class:`dns.name.Name`.
1137 :param parser: The wire format parser.
1138 :type parser: :py:class:`dns.wirebase.Parser`
1139 :raises dns.name.BadPointer: if a compression pointer did not
1140 point backwards in the message.
1141 :raises dns.name.BadLabelType: if an invalid label type was encountered.
1142 :rtype: :py:class:`dns.name.Name`
1143 """
1145 labels = []
1146 biggest_pointer = parser.current
1147 with parser.restore_furthest():
1148 count = parser.get_uint8()
1149 while count != 0:
1150 if count < 64:
1151 labels.append(parser.get_bytes(count))
1152 elif count >= 192:
1153 current = (count & 0x3F) * 256 + parser.get_uint8()
1154 if current >= biggest_pointer:
1155 raise BadPointer
1156 biggest_pointer = current
1157 parser.seek(current)
1158 else:
1159 raise BadLabelType
1160 count = parser.get_uint8()
1161 labels.append(b"")
1162 return Name(labels)
1165def from_wire(message: bytes, current: int) -> tuple[Name, int]:
1166 """Convert possibly compressed wire format into a :py:class:`dns.name.Name`.
1168 :param bytes message: A ``bytes`` containing an entire DNS message in DNS
1169 wire form.
1170 :param int current: The offset of the beginning of the name from the
1171 start of the message.
1172 :raises dns.name.BadPointer: if a compression pointer did not
1173 point backwards in the message.
1174 :raises dns.name.BadLabelType: if an invalid label type was encountered.
1175 :returns: A tuple of the name that was read and the number of bytes of
1176 the wire format message which were consumed reading it.
1177 :rtype: tuple[:py:class:`dns.name.Name`, int]
1178 """
1180 parser = dns.wirebase.Parser(message, current)
1181 name = from_wire_parser(parser)
1182 return (name, parser.current - current)
1185# RFC 4471 Support
1187_MINIMAL_OCTET = b"\x00"
1188_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET)
1189_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET])
1190_MAXIMAL_OCTET = b"\xff"
1191_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET)
1192_AT_SIGN_VALUE = ord("@")
1193_LEFT_SQUARE_BRACKET_VALUE = ord("[")
1196def _wire_length(labels):
1197 return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0)
1200def _pad_to_max_name(name):
1201 needed = 255 - _wire_length(name.labels)
1202 new_labels = []
1203 while needed > 64:
1204 new_labels.append(_MAXIMAL_OCTET * 63)
1205 needed -= 64
1206 if needed >= 2:
1207 new_labels.append(_MAXIMAL_OCTET * (needed - 1))
1208 # Note we're already maximal in the needed == 1 case as while we'd like
1209 # to add one more byte as a new label, we can't, as adding a new non-empty
1210 # label requires at least 2 bytes.
1211 new_labels = list(reversed(new_labels))
1212 new_labels.extend(name.labels)
1213 return Name(new_labels)
1216def _pad_to_max_label(label, suffix_labels):
1217 length = len(label)
1218 # We have to subtract one here to account for the length byte of label.
1219 remaining = 255 - _wire_length(suffix_labels) - length - 1
1220 if remaining <= 0:
1221 # Shouldn't happen!
1222 return label
1223 needed = min(63 - length, remaining)
1224 return label + _MAXIMAL_OCTET * needed
1227def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1228 # This is the RFC 4471 predecessor algorithm using the "absolute method" of section
1229 # 3.1.1.
1230 #
1231 # Our caller must ensure that the name and origin are absolute, and that name is a
1232 # subdomain of origin.
1233 if name == origin:
1234 return _pad_to_max_name(name)
1235 least_significant_label = name[0]
1236 if least_significant_label == _MINIMAL_OCTET:
1237 return name.parent()
1238 least_octet = least_significant_label[-1]
1239 suffix_labels = name.labels[1:]
1240 if least_octet == _MINIMAL_OCTET_VALUE:
1241 new_labels = [least_significant_label[:-1]]
1242 else:
1243 octets = bytearray(least_significant_label)
1244 octet = octets[-1]
1245 if octet == _LEFT_SQUARE_BRACKET_VALUE:
1246 octet = _AT_SIGN_VALUE
1247 else:
1248 octet -= 1
1249 octets[-1] = octet
1250 least_significant_label = bytes(octets)
1251 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)]
1252 new_labels.extend(suffix_labels)
1253 name = Name(new_labels)
1254 if prefix_ok:
1255 return _pad_to_max_name(name)
1256 else:
1257 return name
1260def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1261 # This is the RFC 4471 successor algorithm using the "absolute method" of section
1262 # 3.1.2.
1263 #
1264 # Our caller must ensure that the name and origin are absolute, and that name is a
1265 # subdomain of origin.
1266 if prefix_ok:
1267 # Try prefixing \000 as new label
1268 try:
1269 return _SUCCESSOR_PREFIX.concatenate(name)
1270 except NameTooLong:
1271 pass
1272 while name != origin:
1273 # Try extending the least significant label.
1274 least_significant_label = name[0]
1275 if len(least_significant_label) < 63:
1276 # We may be able to extend the least label with a minimal additional byte.
1277 # This is only "may" because we could have a maximal length name even though
1278 # the least significant label isn't maximally long.
1279 new_labels = [least_significant_label + _MINIMAL_OCTET]
1280 new_labels.extend(name.labels[1:])
1281 try:
1282 return Name(new_labels)
1283 except NameTooLong:
1284 pass
1285 # We can't extend the label either, so we'll try to increment the least
1286 # signficant non-maximal byte in it.
1287 octets = bytearray(least_significant_label)
1288 # We do this reversed iteration with an explicit indexing variable because
1289 # if we find something to increment, we're going to want to truncate everything
1290 # to the right of it.
1291 for i in range(len(octets) - 1, -1, -1):
1292 octet = octets[i]
1293 if octet == _MAXIMAL_OCTET_VALUE:
1294 # We can't increment this, so keep looking.
1295 continue
1296 # Finally, something we can increment. We have to apply a special rule for
1297 # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when
1298 # comparing names, uppercase letters compare as if they were their
1299 # lower-case equivalents. If we increment "@" to "A", then it would compare
1300 # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have
1301 # skipped the most minimal successor, namely "[".
1302 if octet == _AT_SIGN_VALUE:
1303 octet = _LEFT_SQUARE_BRACKET_VALUE
1304 else:
1305 octet += 1
1306 octets[i] = octet
1307 # We can now truncate all of the maximal values we skipped (if any)
1308 new_labels = [bytes(octets[: i + 1])]
1309 new_labels.extend(name.labels[1:])
1310 # We haven't changed the length of the name, so the Name constructor will
1311 # always work.
1312 return Name(new_labels)
1313 # We couldn't increment, so chop off the least significant label and try
1314 # again.
1315 name = name.parent()
1317 # We couldn't increment at all, so return the origin, as wrapping around is the
1318 # DNSSEC way.
1319 return origin
1322def _handle_relativity_and_call(
1323 function: Callable[[Name, Name, bool], Name],
1324 name: Name,
1325 origin: Name,
1326 prefix_ok: bool,
1327) -> Name:
1328 # Make "name" absolute if needed, ensure that the origin is absolute,
1329 # call function(), and then relativize the result if needed.
1330 if not origin.is_absolute():
1331 raise NeedAbsoluteNameOrOrigin
1332 relative = not name.is_absolute()
1333 if relative:
1334 name = name.derelativize(origin)
1335 elif not name.is_subdomain(origin):
1336 raise NeedSubdomainOfOrigin
1337 result_name = function(name, origin, prefix_ok)
1338 if relative:
1339 result_name = result_name.relativize(origin)
1340 return result_name