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.
151 @returns: the escaped string
152 @rtype: string"""
153 if isinstance(label, bytes):
154 # Ordinary DNS label mode. Escape special characters and values
155 # < 0x20 or > 0x7f.
156 text = ""
157 for c in label:
158 if c in _escaped:
159 text += "\\" + chr(c)
160 elif c > 0x20 and c < 0x7F:
161 text += chr(c)
162 else:
163 text += f"\\{c:03d}"
164 return text
166 # Unicode label mode. Escape only special characters and values < 0x20
167 text = ""
168 for uc in label:
169 if uc in _escaped_text:
170 text += "\\" + uc
171 elif uc <= "\x20":
172 text += f"\\{ord(uc):03d}"
173 else:
174 text += uc
175 return text
178class IDNACodec:
179 """Abstract base class for IDNA encoder/decoders."""
181 def __init__(self):
182 pass
184 def is_idna(self, label: bytes) -> bool:
185 return label.lower().startswith(b"xn--")
187 def encode(self, label: str) -> bytes:
188 raise NotImplementedError # pragma: no cover
190 def decode(self, label: bytes) -> str:
191 # We do not apply any IDNA policy on decode.
192 if self.is_idna(label):
193 try:
194 slabel = label[4:].decode("punycode")
195 return _escapify(slabel)
196 except Exception as e:
197 raise IDNAException(idna_exception=e)
198 else:
199 return _escapify(label)
202class IDNA2003Codec(IDNACodec):
203 """IDNA 2003 encoder/decoder."""
205 def __init__(self, strict_decode: bool = False):
206 """Initialize the IDNA 2003 encoder/decoder.
208 *strict_decode* is a ``bool``. If `True`, then IDNA2003 checking
209 is done when decoding. This can cause failures if the name
210 was encoded with IDNA2008. The default is `False`.
211 """
213 super().__init__()
214 self.strict_decode = strict_decode
216 def encode(self, label: str) -> bytes:
217 """Encode *label*."""
219 if label == "":
220 return b""
221 try:
222 return encodings.idna.ToASCII(label)
223 except UnicodeError:
224 raise LabelTooLong
226 def decode(self, label: bytes) -> str:
227 """Decode *label*."""
228 if not self.strict_decode:
229 return super().decode(label)
230 if label == b"":
231 return ""
232 try:
233 return _escapify(encodings.idna.ToUnicode(label))
234 except Exception as e:
235 raise IDNAException(idna_exception=e)
238class IDNA2008Codec(IDNACodec):
239 """IDNA 2008 encoder/decoder."""
241 def __init__(
242 self,
243 uts_46: bool = False,
244 transitional: bool = False,
245 allow_pure_ascii: bool = False,
246 strict_decode: bool = False,
247 ):
248 """Initialize the IDNA 2008 encoder/decoder.
250 *uts_46* is a ``bool``. If True, apply Unicode IDNA
251 compatibility processing as described in Unicode Technical
252 Standard #46 (https://unicode.org/reports/tr46/).
253 If False, do not apply the mapping. The default is False.
255 *transitional* is a ``bool``: If True, use the
256 "transitional" mode described in Unicode Technical Standard
257 #46. The default is False. This setting has no effect
258 in idna 3.11 and later as transitional support has been removed.
260 *allow_pure_ascii* is a ``bool``. 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 with starting with "_sip._tcp." and ending
264 in an IDN suffix which would otherwise be disallowed. The
265 default is False.
267 *strict_decode* is a ``bool``: If True, then IDNA2008 checking
268 is done when decoding. This can cause failures if the name
269 was encoded with IDNA2003. The default is False.
270 """
271 super().__init__()
272 self.uts_46 = uts_46
273 self.transitional = transitional
274 self.allow_pure_ascii = allow_pure_ascii
275 self.strict_decode = strict_decode
277 def encode(self, label: str) -> bytes:
278 if label == "":
279 return b""
280 if self.allow_pure_ascii and is_all_ascii(label):
281 encoded = label.encode("ascii")
282 if len(encoded) > 63:
283 raise LabelTooLong
284 return encoded
285 if not have_idna_2008:
286 raise NoIDNA2008
287 try:
288 if self.uts_46:
289 # pylint: disable=possibly-used-before-assignment
290 label = idna.uts46_remap(label, False, self.transitional)
291 return idna.alabel(label)
292 except idna.IDNAError as e:
293 if e.args[0] == "Label too long":
294 raise LabelTooLong
295 else:
296 raise IDNAException(idna_exception=e)
298 def decode(self, label: bytes) -> str:
299 if not self.strict_decode:
300 return super().decode(label)
301 if label == b"":
302 return ""
303 if not have_idna_2008:
304 raise NoIDNA2008
305 try:
306 ulabel = idna.ulabel(label)
307 if self.uts_46:
308 ulabel = idna.uts46_remap(ulabel, False, self.transitional)
309 return _escapify(ulabel)
310 except (idna.IDNAError, UnicodeError) as e:
311 raise IDNAException(idna_exception=e)
314IDNA_2003_Practical = IDNA2003Codec(False)
315IDNA_2003_Strict = IDNA2003Codec(True)
316IDNA_2003 = IDNA_2003_Practical
317IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)
318IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)
319IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)
320IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)
321IDNA_2008 = IDNA_2008_Practical
322if have_idna_2008:
323 IDNA_DEFAULT = IDNA_2008_Practical
324else:
325 IDNA_DEFAULT = IDNA_2003_Practical
328def set_default_idna_codec(idna_codec: IDNACodec):
329 """Set the default IDNA codec."""
330 global IDNA_DEFAULT
331 IDNA_DEFAULT = idna_codec
334def _validate_labels(labels: tuple[bytes, ...]) -> None:
335 """Check for empty labels in the middle of a label sequence,
336 labels that are too long, and for too many labels.
338 Raises ``dns.name.NameTooLong`` if the name as a whole is too long.
340 Raises ``dns.name.EmptyLabel`` if a label is empty (i.e. the root
341 label) and appears in a position other than the end of the label
342 sequence
344 """
346 l = len(labels)
347 total = 0
348 i = -1
349 j = 0
350 for label in labels:
351 ll = len(label)
352 total += ll + 1
353 if ll > 63:
354 raise LabelTooLong
355 if i < 0 and label == b"":
356 i = j
357 j += 1
358 if total > 255:
359 raise NameTooLong
360 if i >= 0 and i != l - 1:
361 raise EmptyLabel
364def _maybe_convert_to_binary(label: bytes | str) -> bytes:
365 """If label is ``str``, convert it to ``bytes``. If it is already
366 ``bytes`` just return it.
368 """
370 if isinstance(label, bytes):
371 return label
372 else:
373 return label.encode()
376@dataclasses.dataclass(frozen=True)
377class NameStyle(BaseStyle):
378 """Name text styles
380 *omit_final_dot* is a ``bool``. If True, don't emit the final
381 dot (denoting the root label) for absolute names. The default
382 is False.
384 *idna_codec* specifies the IDNA decoder to use. The default is ``None``
385 which means all text is in the standard DNS zonefile format, i.e.
386 punycode will not be decoded.
388 If *origin* is ``None``, the default, then the name's relativity is not
389 altered before conversion to text. Otherwise, if *relativize* is ``True``
390 the name is relativized, and if *relativize* is ``False`` the name is
391 derelativized.
392 """
394 omit_final_dot: bool = False
395 idna_codec: IDNACodec | None = None
396 origin: "Name | None" = None
397 relativize: bool = False
400@dns.immutable.immutable
401class Name:
402 """A DNS name.
404 The dns.name.Name class represents a DNS name as a tuple of
405 labels. Each label is a ``bytes`` in DNS wire format. Instances
406 of the class are immutable.
407 """
409 __slots__ = ["labels"]
411 def __init__(self, labels: Iterable[bytes | str]):
412 """*labels* is any iterable whose values are ``str`` or ``bytes``."""
414 blabels = [_maybe_convert_to_binary(x) for x in labels]
415 self.labels = tuple(blabels)
416 _validate_labels(self.labels)
418 def __copy__(self):
419 return Name(self.labels)
421 def __deepcopy__(self, memo):
422 return Name(copy.deepcopy(self.labels, memo))
424 def __getstate__(self):
425 # Names can be pickled
426 return {"labels": self.labels}
428 def __setstate__(self, state):
429 super().__setattr__("labels", state["labels"])
430 _validate_labels(self.labels)
432 def is_absolute(self) -> bool:
433 """Is the most significant label of this name the root label?
435 Returns a ``bool``.
436 """
438 return len(self.labels) > 0 and self.labels[-1] == b""
440 def is_wild(self) -> bool:
441 """Is this name wild? (I.e. Is the least significant label '*'?)
443 Returns a ``bool``.
444 """
446 return len(self.labels) > 0 and self.labels[0] == b"*"
448 def __hash__(self) -> int:
449 """Return a case-insensitive hash of the name.
451 Returns an ``int``.
452 """
454 h = 0
455 for label in self.labels:
456 for c in label.lower():
457 h += (h << 3) + c
458 return h
460 def fullcompare(self, other: "Name") -> tuple[NameRelation, int, int]:
461 """Compare two names, returning a 3-tuple
462 ``(relation, order, nlabels)``.
464 *relation* describes the relation ship between the names,
465 and is one of: ``dns.name.NameRelation.NONE``,
466 ``dns.name.NameRelation.SUPERDOMAIN``, ``dns.name.NameRelation.SUBDOMAIN``,
467 ``dns.name.NameRelation.EQUAL``, or ``dns.name.NameRelation.COMMONANCESTOR``.
469 *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==
470 0 if *self* == *other*. A relative name is always less than an
471 absolute name. If both names have the same relativity, then
472 the DNSSEC order relation is used to order them.
474 *nlabels* is the number of significant labels that the two names
475 have in common.
477 Here are some examples. Names ending in "." are absolute names,
478 those not ending in "." are relative names.
480 ============= ============= =========== ===== =======
481 self other relation order nlabels
482 ============= ============= =========== ===== =======
483 www.example. www.example. equal 0 3
484 www.example. example. subdomain > 0 2
485 example. www.example. superdomain < 0 2
486 example1.com. example2.com. common anc. < 0 2
487 example1 example2. none < 0 0
488 example1. example2 none > 0 0
489 ============= ============= =========== ===== =======
490 """
492 sabs = self.is_absolute()
493 oabs = other.is_absolute()
494 if sabs != oabs:
495 if sabs:
496 return (NameRelation.NONE, 1, 0)
497 else:
498 return (NameRelation.NONE, -1, 0)
499 l1 = len(self.labels)
500 l2 = len(other.labels)
501 ldiff = l1 - l2
502 if ldiff < 0:
503 l = l1
504 else:
505 l = l2
507 order = 0
508 nlabels = 0
509 namereln = NameRelation.NONE
510 while l > 0:
511 l -= 1
512 l1 -= 1
513 l2 -= 1
514 label1 = self.labels[l1].lower()
515 label2 = other.labels[l2].lower()
516 if label1 < label2:
517 order = -1
518 if nlabels > 0:
519 namereln = NameRelation.COMMONANCESTOR
520 return (namereln, order, nlabels)
521 elif label1 > label2:
522 order = 1
523 if nlabels > 0:
524 namereln = NameRelation.COMMONANCESTOR
525 return (namereln, order, nlabels)
526 nlabels += 1
527 order = ldiff
528 if ldiff < 0:
529 namereln = NameRelation.SUPERDOMAIN
530 elif ldiff > 0:
531 namereln = NameRelation.SUBDOMAIN
532 else:
533 namereln = NameRelation.EQUAL
534 return (namereln, order, nlabels)
536 def is_subdomain(self, other: "Name") -> bool:
537 """Is self a subdomain of other?
539 Note that the notion of subdomain includes equality, e.g.
540 "dnspython.org" is a subdomain of itself.
542 Returns a ``bool``.
543 """
545 nr, _, _ = self.fullcompare(other)
546 if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL:
547 return True
548 return False
550 def is_superdomain(self, other: "Name") -> bool:
551 """Is self a superdomain of other?
553 Note that the notion of superdomain includes equality, e.g.
554 "dnspython.org" is a superdomain of itself.
556 Returns a ``bool``.
557 """
559 nr, _, _ = self.fullcompare(other)
560 if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL:
561 return True
562 return False
564 def canonicalize(self) -> "Name":
565 """Return a name which is equal to the current name, but is in
566 DNSSEC canonical form.
567 """
569 return Name([x.lower() for x in self.labels])
571 def __eq__(self, other):
572 if isinstance(other, Name):
573 return self.fullcompare(other)[1] == 0
574 else:
575 return False
577 def __ne__(self, other):
578 if isinstance(other, Name):
579 return self.fullcompare(other)[1] != 0
580 else:
581 return True
583 def __lt__(self, other):
584 if isinstance(other, Name):
585 return self.fullcompare(other)[1] < 0
586 else:
587 return NotImplemented
589 def __le__(self, other):
590 if isinstance(other, Name):
591 return self.fullcompare(other)[1] <= 0
592 else:
593 return NotImplemented
595 def __ge__(self, other):
596 if isinstance(other, Name):
597 return self.fullcompare(other)[1] >= 0
598 else:
599 return NotImplemented
601 def __gt__(self, other):
602 if isinstance(other, Name):
603 return self.fullcompare(other)[1] > 0
604 else:
605 return NotImplemented
607 def __repr__(self):
608 return "<DNS name " + self.__str__() + ">"
610 def __str__(self):
611 return self.to_text(False)
613 def to_text(
614 self, omit_final_dot: bool = False, style: NameStyle | None = None
615 ) -> str:
616 """Convert name to DNS text format.
618 *omit_final_dot* is a ``bool``. If True, don't emit the final
619 dot (denoting the root label) for absolute names. The default
620 is False.
622 *style*, a :py:class:`dns.name.NameStyle` or ``None`` (the default). If
623 specified, the style overrides the other parameters.
625 Returns a ``str``.
626 """
627 if style is None:
628 style = NameStyle(omit_final_dot=omit_final_dot)
629 return self.to_styled_text(style)
631 def to_unicode(
632 self,
633 omit_final_dot: bool = False,
634 idna_codec: IDNACodec | None = None,
635 style: NameStyle | None = None,
636 ) -> str:
637 """Convert name to DNS text format.
639 IDN ACE labels are converted to Unicode using the specified codec.
641 *omit_final_dot* is a ``bool``. If True, don't emit the final
642 dot (denoting the root label) for absolute names. The default
643 is False.
645 Returns a ``str``.
646 """
647 if idna_codec is None:
648 idna_codec = IDNA_DEFAULT
649 if style is None:
650 style = NameStyle(omit_final_dot=omit_final_dot, idna_codec=idna_codec)
651 return self.to_styled_text(style)
653 def to_styled_text(self, style: NameStyle) -> str:
654 """Convert name to text format, applying the style.
656 See the documentation for :py:class:`dns.name.NameStyle` for a description
657 of the style parameters.
659 Returns a ``str``.
660 """
662 name = self.choose_relativity(style.origin, style.relativize)
663 if len(name.labels) == 0:
664 return "@"
665 if len(name.labels) == 1 and name.labels[0] == b"":
666 return "."
667 if style.omit_final_dot and name.is_absolute():
668 l = name.labels[:-1]
669 else:
670 l = name.labels
671 if style.idna_codec is None:
672 return ".".join(map(_escapify, l))
673 else:
674 return ".".join([style.idna_codec.decode(x) for x in l])
676 def to_digestable(self, origin: "Name | None" = None) -> bytes:
677 """Convert name to a format suitable for digesting in hashes.
679 The name is canonicalized and converted to uncompressed wire
680 format. All names in wire format are absolute. If the name
681 is a relative name, then an origin must be supplied.
683 *origin* is a ``dns.name.Name`` or ``None``. If the name is
684 relative and origin is not ``None``, then origin will be appended
685 to the name.
687 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
688 relative and no origin was provided.
690 Returns a ``bytes``.
691 """
693 digest = self.to_wire(origin=origin, canonicalize=True)
694 assert digest is not None
695 return digest
697 def to_wire(
698 self,
699 file: Any | None = None,
700 compress: CompressType | None = None,
701 origin: "Name | None" = None,
702 canonicalize: bool = False,
703 ) -> bytes | None:
704 """Convert name to wire format, possibly compressing it.
706 *file* is the file where the name is emitted (typically an
707 io.BytesIO file). If ``None`` (the default), a ``bytes``
708 containing the wire name will be returned.
710 *compress*, a ``dict``, is the compression table to use. If
711 ``None`` (the default), names will not be compressed. Note that
712 the compression code assumes that compression offset 0 is the
713 start of *file*, and thus compression will not be correct
714 if this is not the case.
716 *origin* is a ``dns.name.Name`` or ``None``. If the name is
717 relative and origin is not ``None``, then *origin* will be appended
718 to it.
720 *canonicalize*, a ``bool``, indicates whether the name should
721 be canonicalized; that is, converted to a format suitable for
722 digesting in hashes.
724 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
725 relative and no origin was provided.
727 Returns a ``bytes`` or ``None``.
728 """
730 if file is None:
731 out = bytearray()
732 for label in self.labels:
733 out.append(len(label))
734 if canonicalize:
735 out += label.lower()
736 else:
737 out += label
738 if not self.is_absolute():
739 if origin is None or not origin.is_absolute():
740 raise NeedAbsoluteNameOrOrigin
741 for label in origin.labels:
742 out.append(len(label))
743 if canonicalize:
744 out += label.lower()
745 else:
746 out += label
747 return bytes(out)
749 labels: Iterable[bytes]
750 if not self.is_absolute():
751 if origin is None or not origin.is_absolute():
752 raise NeedAbsoluteNameOrOrigin
753 labels = list(self.labels)
754 labels.extend(list(origin.labels))
755 else:
756 labels = self.labels
757 i = 0
758 for label in labels:
759 n = Name(labels[i:])
760 i += 1
761 if compress is not None:
762 pos = compress.get(n)
763 else:
764 pos = None
765 if pos is not None:
766 value = 0xC000 + pos
767 s = struct.pack("!H", value)
768 file.write(s)
769 break
770 else:
771 if compress is not None and len(n) > 1:
772 pos = file.tell()
773 if pos <= 0x3FFF:
774 compress[n] = pos
775 l = len(label)
776 file.write(struct.pack("!B", l))
777 if l > 0:
778 if canonicalize:
779 file.write(label.lower())
780 else:
781 file.write(label)
782 return None
784 def __len__(self) -> int:
785 """The length of the name (in labels).
787 Returns an ``int``.
788 """
790 return len(self.labels)
792 def __getitem__(self, index: Any) -> Any:
793 return self.labels[index]
795 def __add__(self, other):
796 return self.concatenate(other)
798 def __sub__(self, other):
799 return self.relativize(other)
801 def split(self, depth: int) -> tuple["Name", "Name"]:
802 """Split a name into a prefix and suffix names at the specified depth.
804 *depth* is an ``int`` specifying the number of labels in the suffix
806 Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the
807 name.
809 Returns the tuple ``(prefix, suffix)``.
810 """
812 l = len(self.labels)
813 if depth == 0:
814 return (self, empty)
815 elif depth == l:
816 return (empty, self)
817 elif depth < 0 or depth > l:
818 raise ValueError("depth must be >= 0 and <= the length of the name")
819 return (Name(self[:-depth]), Name(self[-depth:]))
821 def concatenate(self, other: "Name") -> "Name":
822 """Return a new name which is the concatenation of self and other.
824 Raises ``dns.name.AbsoluteConcatenation`` if the name is
825 absolute and *other* is not the empty name.
827 Returns a ``dns.name.Name``.
828 """
830 if self.is_absolute() and len(other) > 0:
831 raise AbsoluteConcatenation
832 labels = list(self.labels)
833 labels.extend(list(other.labels))
834 return Name(labels)
836 def relativize(self, origin: "Name") -> "Name":
837 """If the name is a subdomain of *origin*, return a new name which is
838 the name relative to origin. Otherwise return the name.
840 For example, relativizing ``www.dnspython.org.`` to origin
841 ``dnspython.org.`` returns the name ``www``. Relativizing ``example.``
842 to origin ``dnspython.org.`` returns ``example.``.
844 Returns a ``dns.name.Name``.
845 """
847 if self.is_subdomain(origin):
848 return Name(self[: -len(origin)])
849 else:
850 return self
852 def derelativize(self, origin: "Name") -> "Name":
853 """If the name is a relative name, return a new name which is the
854 concatenation of the name and origin. Otherwise return the name.
856 For example, derelativizing ``www`` to origin ``dnspython.org.``
857 returns the name ``www.dnspython.org.``. Derelativizing ``example.``
858 to origin ``dnspython.org.`` returns ``example.``.
860 Returns a ``dns.name.Name``.
861 """
863 if not self.is_absolute():
864 return self.concatenate(origin)
865 else:
866 return self
868 def choose_relativity(
869 self, origin: "Name | None" = None, relativize: bool = True
870 ) -> "Name":
871 """Return a name with the relativity desired by the caller.
873 If *origin* is ``None``, then the name is returned.
874 Otherwise, if *relativize* is ``True`` the name is
875 relativized, and if *relativize* is ``False`` the name is
876 derelativized.
878 Returns a ``dns.name.Name``.
879 """
881 if origin:
882 if relativize:
883 return self.relativize(origin)
884 else:
885 return self.derelativize(origin)
886 else:
887 return self
889 def parent(self) -> "Name":
890 """Return the parent of the name.
892 For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``.
894 Raises ``dns.name.NoParent`` if the name is either the root name or the
895 empty name, and thus has no parent.
897 Returns a ``dns.name.Name``.
898 """
900 if self == root or self == empty:
901 raise NoParent
902 return Name(self.labels[1:])
904 def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
905 """Return the maximal predecessor of *name* in the DNSSEC ordering in the zone
906 whose origin is *origin*, or return the longest name under *origin* if the
907 name is origin (i.e. wrap around to the longest name, which may still be
908 *origin* due to length considerations.
910 The relativity of the name is preserved, so if this name is relative
911 then the method will return a relative name, and likewise if this name
912 is absolute then the predecessor will be absolute.
914 *prefix_ok* indicates if prefixing labels is allowed, and
915 defaults to ``True``. Normally it is good to allow this, but if computing
916 a maximal predecessor at a zone cut point then ``False`` must be specified.
917 """
918 return _handle_relativity_and_call(
919 _absolute_predecessor, self, origin, prefix_ok
920 )
922 def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
923 """Return the minimal successor of *name* in the DNSSEC ordering in the zone
924 whose origin is *origin*, or return *origin* if the successor cannot be
925 computed due to name length limitations.
927 Note that *origin* is returned in the "too long" cases because wrapping
928 around to the origin is how NSEC records express "end of the zone".
930 The relativity of the name is preserved, so if this name is relative
931 then the method will return a relative name, and likewise if this name
932 is absolute then the successor will be absolute.
934 *prefix_ok* indicates if prefixing a new minimal label is allowed, and
935 defaults to ``True``. Normally it is good to allow this, but if computing
936 a minimal successor at a zone cut point then ``False`` must be specified.
937 """
938 return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok)
941#: The root name, '.'
942root = Name([b""])
944#: The empty name.
945empty = Name([])
948def from_unicode(
949 text: str, origin: Name | None = root, idna_codec: IDNACodec | None = None
950) -> Name:
951 """Convert unicode text into a Name object.
953 Labels are encoded in IDN ACE form according to rules specified by
954 the IDNA codec.
956 *text*, a ``str``, is the text to convert into a name.
958 *origin*, a ``dns.name.Name``, specifies the origin to
959 append to non-absolute names. The default is the root name.
961 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
962 encoder/decoder. If ``None``, the default IDNA encoder/decoder
963 is used.
965 Returns a ``dns.name.Name``.
966 """
968 labels = []
969 label = ""
970 escaping = False
971 edigits = 0
972 total = 0
973 if idna_codec is None:
974 idna_codec = IDNA_DEFAULT
975 if text == "@":
976 text = ""
977 if text:
978 if text in [".", "\u3002", "\uff0e", "\uff61"]:
979 return Name([b""]) # no Unicode "u" on this constant!
980 for c in text:
981 if escaping:
982 if edigits == 0:
983 if c.isdigit():
984 total = int(c)
985 edigits += 1
986 else:
987 label += c
988 escaping = False
989 else:
990 if not c.isdigit():
991 raise BadEscape
992 total *= 10
993 total += int(c)
994 edigits += 1
995 if edigits == 3:
996 escaping = False
997 label += chr(total)
998 elif c in [".", "\u3002", "\uff0e", "\uff61"]:
999 if len(label) == 0:
1000 raise EmptyLabel
1001 labels.append(idna_codec.encode(label))
1002 label = ""
1003 elif c == "\\":
1004 escaping = True
1005 edigits = 0
1006 total = 0
1007 else:
1008 label += c
1009 if escaping:
1010 raise BadEscape
1011 if len(label) > 0:
1012 labels.append(idna_codec.encode(label))
1013 else:
1014 labels.append(b"")
1016 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
1017 labels.extend(list(origin.labels))
1018 return Name(labels)
1021def is_all_ascii(text: str) -> bool:
1022 for c in text:
1023 if ord(c) > 0x7F:
1024 return False
1025 return True
1028def from_text(
1029 text: bytes | str,
1030 origin: Name | None = root,
1031 idna_codec: IDNACodec | None = None,
1032) -> Name:
1033 """Convert text into a Name object.
1035 *text*, a ``bytes`` or ``str``, is the text to convert into a name.
1037 *origin*, a ``dns.name.Name``, specifies the origin to
1038 append to non-absolute names. The default is the root name.
1040 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
1041 encoder/decoder. If ``None``, the default IDNA encoder/decoder
1042 is used.
1044 Returns a ``dns.name.Name``.
1045 """
1047 if isinstance(text, str):
1048 if not is_all_ascii(text):
1049 # Some codepoint in the input text is > 127, so IDNA applies.
1050 return from_unicode(text, origin, idna_codec)
1051 # The input is all ASCII, so treat this like an ordinary non-IDNA
1052 # domain name. Note that "all ASCII" is about the input text,
1053 # not the codepoints in the domain name. E.g. if text has value
1054 #
1055 # r'\150\151\152\153\154\155\156\157\158\159'
1056 #
1057 # then it's still "all ASCII" even though the domain name has
1058 # codepoints > 127.
1059 text = text.encode("ascii")
1060 labels = []
1061 label = b""
1062 escaping = False
1063 edigits = 0
1064 total = 0
1065 if text == b"@":
1066 text = b""
1067 if text:
1068 if text == b".":
1069 return Name([b""])
1070 for c in text:
1071 byte_ = struct.pack("!B", c)
1072 if escaping:
1073 if edigits == 0:
1074 if byte_.isdigit():
1075 total = int(byte_)
1076 edigits += 1
1077 else:
1078 label += byte_
1079 escaping = False
1080 else:
1081 if not byte_.isdigit():
1082 raise BadEscape
1083 total *= 10
1084 total += int(byte_)
1085 edigits += 1
1086 if edigits == 3:
1087 escaping = False
1088 label += struct.pack("!B", total)
1089 elif byte_ == b".":
1090 if len(label) == 0:
1091 raise EmptyLabel
1092 labels.append(label)
1093 label = b""
1094 elif byte_ == b"\\":
1095 escaping = True
1096 edigits = 0
1097 total = 0
1098 else:
1099 label += byte_
1100 if escaping:
1101 raise BadEscape
1102 if len(label) > 0:
1103 labels.append(label)
1104 else:
1105 labels.append(b"")
1106 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
1107 labels.extend(list(origin.labels))
1108 return Name(labels)
1111def from_wire_parser(parser: dns.wirebase.Parser) -> Name:
1112 """Convert possibly compressed wire format into a Name.
1114 *parser* is a dns.wirebase.Parser.
1116 Raises ``dns.name.BadPointer`` if a compression pointer did not
1117 point backwards in the message.
1119 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
1121 Returns a ``dns.name.Name``
1122 """
1124 labels = []
1125 biggest_pointer = parser.current
1126 with parser.restore_furthest():
1127 count = parser.get_uint8()
1128 while count != 0:
1129 if count < 64:
1130 labels.append(parser.get_bytes(count))
1131 elif count >= 192:
1132 current = (count & 0x3F) * 256 + parser.get_uint8()
1133 if current >= biggest_pointer:
1134 raise BadPointer
1135 biggest_pointer = current
1136 parser.seek(current)
1137 else:
1138 raise BadLabelType
1139 count = parser.get_uint8()
1140 labels.append(b"")
1141 return Name(labels)
1144def from_wire(message: bytes, current: int) -> tuple[Name, int]:
1145 """Convert possibly compressed wire format into a Name.
1147 *message* is a ``bytes`` containing an entire DNS message in DNS
1148 wire form.
1150 *current*, an ``int``, is the offset of the beginning of the name
1151 from the start of the message
1153 Raises ``dns.name.BadPointer`` if a compression pointer did not
1154 point backwards in the message.
1156 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
1158 Returns a ``(dns.name.Name, int)`` tuple consisting of the name
1159 that was read and the number of bytes of the wire format message
1160 which were consumed reading it.
1161 """
1163 parser = dns.wirebase.Parser(message, current)
1164 name = from_wire_parser(parser)
1165 return (name, parser.current - current)
1168# RFC 4471 Support
1170_MINIMAL_OCTET = b"\x00"
1171_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET)
1172_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET])
1173_MAXIMAL_OCTET = b"\xff"
1174_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET)
1175_AT_SIGN_VALUE = ord("@")
1176_LEFT_SQUARE_BRACKET_VALUE = ord("[")
1179def _wire_length(labels):
1180 return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0)
1183def _pad_to_max_name(name):
1184 needed = 255 - _wire_length(name.labels)
1185 new_labels = []
1186 while needed > 64:
1187 new_labels.append(_MAXIMAL_OCTET * 63)
1188 needed -= 64
1189 if needed >= 2:
1190 new_labels.append(_MAXIMAL_OCTET * (needed - 1))
1191 # Note we're already maximal in the needed == 1 case as while we'd like
1192 # to add one more byte as a new label, we can't, as adding a new non-empty
1193 # label requires at least 2 bytes.
1194 new_labels = list(reversed(new_labels))
1195 new_labels.extend(name.labels)
1196 return Name(new_labels)
1199def _pad_to_max_label(label, suffix_labels):
1200 length = len(label)
1201 # We have to subtract one here to account for the length byte of label.
1202 remaining = 255 - _wire_length(suffix_labels) - length - 1
1203 if remaining <= 0:
1204 # Shouldn't happen!
1205 return label
1206 needed = min(63 - length, remaining)
1207 return label + _MAXIMAL_OCTET * needed
1210def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1211 # This is the RFC 4471 predecessor algorithm using the "absolute method" of section
1212 # 3.1.1.
1213 #
1214 # Our caller must ensure that the name and origin are absolute, and that name is a
1215 # subdomain of origin.
1216 if name == origin:
1217 return _pad_to_max_name(name)
1218 least_significant_label = name[0]
1219 if least_significant_label == _MINIMAL_OCTET:
1220 return name.parent()
1221 least_octet = least_significant_label[-1]
1222 suffix_labels = name.labels[1:]
1223 if least_octet == _MINIMAL_OCTET_VALUE:
1224 new_labels = [least_significant_label[:-1]]
1225 else:
1226 octets = bytearray(least_significant_label)
1227 octet = octets[-1]
1228 if octet == _LEFT_SQUARE_BRACKET_VALUE:
1229 octet = _AT_SIGN_VALUE
1230 else:
1231 octet -= 1
1232 octets[-1] = octet
1233 least_significant_label = bytes(octets)
1234 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)]
1235 new_labels.extend(suffix_labels)
1236 name = Name(new_labels)
1237 if prefix_ok:
1238 return _pad_to_max_name(name)
1239 else:
1240 return name
1243def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1244 # This is the RFC 4471 successor algorithm using the "absolute method" of section
1245 # 3.1.2.
1246 #
1247 # Our caller must ensure that the name and origin are absolute, and that name is a
1248 # subdomain of origin.
1249 if prefix_ok:
1250 # Try prefixing \000 as new label
1251 try:
1252 return _SUCCESSOR_PREFIX.concatenate(name)
1253 except NameTooLong:
1254 pass
1255 while name != origin:
1256 # Try extending the least significant label.
1257 least_significant_label = name[0]
1258 if len(least_significant_label) < 63:
1259 # We may be able to extend the least label with a minimal additional byte.
1260 # This is only "may" because we could have a maximal length name even though
1261 # the least significant label isn't maximally long.
1262 new_labels = [least_significant_label + _MINIMAL_OCTET]
1263 new_labels.extend(name.labels[1:])
1264 try:
1265 return Name(new_labels)
1266 except NameTooLong:
1267 pass
1268 # We can't extend the label either, so we'll try to increment the least
1269 # signficant non-maximal byte in it.
1270 octets = bytearray(least_significant_label)
1271 # We do this reversed iteration with an explicit indexing variable because
1272 # if we find something to increment, we're going to want to truncate everything
1273 # to the right of it.
1274 for i in range(len(octets) - 1, -1, -1):
1275 octet = octets[i]
1276 if octet == _MAXIMAL_OCTET_VALUE:
1277 # We can't increment this, so keep looking.
1278 continue
1279 # Finally, something we can increment. We have to apply a special rule for
1280 # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when
1281 # comparing names, uppercase letters compare as if they were their
1282 # lower-case equivalents. If we increment "@" to "A", then it would compare
1283 # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have
1284 # skipped the most minimal successor, namely "[".
1285 if octet == _AT_SIGN_VALUE:
1286 octet = _LEFT_SQUARE_BRACKET_VALUE
1287 else:
1288 octet += 1
1289 octets[i] = octet
1290 # We can now truncate all of the maximal values we skipped (if any)
1291 new_labels = [bytes(octets[: i + 1])]
1292 new_labels.extend(name.labels[1:])
1293 # We haven't changed the length of the name, so the Name constructor will
1294 # always work.
1295 return Name(new_labels)
1296 # We couldn't increment, so chop off the least significant label and try
1297 # again.
1298 name = name.parent()
1300 # We couldn't increment at all, so return the origin, as wrapping around is the
1301 # DNSSEC way.
1302 return origin
1305def _handle_relativity_and_call(
1306 function: Callable[[Name, Name, bool], Name],
1307 name: Name,
1308 origin: Name,
1309 prefix_ok: bool,
1310) -> Name:
1311 # Make "name" absolute if needed, ensure that the origin is absolute,
1312 # call function(), and then relativize the result if needed.
1313 if not origin.is_absolute():
1314 raise NeedAbsoluteNameOrOrigin
1315 relative = not name.is_absolute()
1316 if relative:
1317 name = name.derelativize(origin)
1318 elif not name.is_subdomain(origin):
1319 raise NeedSubdomainOfOrigin
1320 result_name = function(name, origin, prefix_ok)
1321 if relative:
1322 result_name = result_name.relativize(origin)
1323 return result_name