Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dns/name.py: 25%
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 encodings.idna # type: ignore
22import functools
23import struct
24from collections.abc import Callable, Iterable
25from typing import Any
27import dns._features
28import dns.enum
29import dns.exception
30import dns.immutable
31import dns.wirebase
33# Dnspython will never access idna if the import fails, but pyright can't figure
34# that out, so...
35#
36# pyright: reportAttributeAccessIssue = false, reportPossiblyUnboundVariable = false
38if dns._features.have("idna"):
39 import idna # type: ignore
41 have_idna_2008 = True
42else: # pragma: no cover
43 have_idna_2008 = False
46CompressType = dict["Name", int]
49class NameRelation(dns.enum.IntEnum):
50 """Name relation result from fullcompare()."""
52 # This is an IntEnum for backwards compatibility in case anyone
53 # has hardwired the constants.
55 #: The compared names have no relationship to each other.
56 NONE = 0
57 #: the first name is a superdomain of the second.
58 SUPERDOMAIN = 1
59 #: The first name is a subdomain of the second.
60 SUBDOMAIN = 2
61 #: The compared names are equal.
62 EQUAL = 3
63 #: The compared names have a common ancestor.
64 COMMONANCESTOR = 4
66 @classmethod
67 def _maximum(cls):
68 return cls.COMMONANCESTOR # pragma: no cover
70 @classmethod
71 def _short_name(cls):
72 return cls.__name__ # pragma: no cover
75# Backwards compatibility
76NAMERELN_NONE = NameRelation.NONE
77NAMERELN_SUPERDOMAIN = NameRelation.SUPERDOMAIN
78NAMERELN_SUBDOMAIN = NameRelation.SUBDOMAIN
79NAMERELN_EQUAL = NameRelation.EQUAL
80NAMERELN_COMMONANCESTOR = NameRelation.COMMONANCESTOR
83class EmptyLabel(dns.exception.SyntaxError):
84 """A DNS label is empty."""
87class BadEscape(dns.exception.SyntaxError):
88 """An escaped code in a text format of DNS name is invalid."""
91class BadPointer(dns.exception.FormError):
92 """A DNS compression pointer points forward instead of backward."""
95class BadLabelType(dns.exception.FormError):
96 """The label type in DNS name wire format is unknown."""
99class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):
100 """An attempt was made to convert a non-absolute name to
101 wire when there was also a non-absolute (or missing) origin."""
104class NameTooLong(dns.exception.FormError):
105 """A DNS name is > 255 octets long."""
108class LabelTooLong(dns.exception.SyntaxError):
109 """A DNS label is > 63 octets long."""
112class AbsoluteConcatenation(dns.exception.DNSException):
113 """An attempt was made to append anything other than the
114 empty name to an absolute DNS name."""
117class NoParent(dns.exception.DNSException):
118 """An attempt was made to get the parent of the root name
119 or the empty name."""
122class NoIDNA2008(dns.exception.DNSException):
123 """IDNA 2008 processing was requested but the idna module is not
124 available."""
127class IDNAException(dns.exception.DNSException):
128 """IDNA processing raised an exception."""
130 supp_kwargs = {"idna_exception"}
131 fmt = "IDNA processing exception: {idna_exception}"
133 # We do this as otherwise mypy complains about unexpected keyword argument
134 # idna_exception
135 def __init__(self, *args, **kwargs):
136 super().__init__(*args, **kwargs)
139class NeedSubdomainOfOrigin(dns.exception.DNSException):
140 """An absolute name was provided that is not a subdomain of the specified origin."""
143_escaped = b'"().;\\@$'
144_escaped_text = '"().;\\@$'
147def _escapify(label: bytes | str) -> str:
148 """Escape the characters in label which need it.
149 @returns: the escaped string
150 @rtype: string"""
151 if isinstance(label, bytes):
152 # Ordinary DNS label mode. Escape special characters and values
153 # < 0x20 or > 0x7f.
154 text = ""
155 for c in label:
156 if c in _escaped:
157 text += "\\" + chr(c)
158 elif c > 0x20 and c < 0x7F:
159 text += chr(c)
160 else:
161 text += f"\\{c:03d}"
162 return text
164 # Unicode label mode. Escape only special characters and values < 0x20
165 text = ""
166 for uc in label:
167 if uc in _escaped_text:
168 text += "\\" + uc
169 elif uc <= "\x20":
170 text += f"\\{ord(uc):03d}"
171 else:
172 text += uc
173 return text
176class IDNACodec:
177 """Abstract base class for IDNA encoder/decoders."""
179 def __init__(self):
180 pass
182 def is_idna(self, label: bytes) -> bool:
183 return label.lower().startswith(b"xn--")
185 def encode(self, label: str) -> bytes:
186 raise NotImplementedError # pragma: no cover
188 def decode(self, label: bytes) -> str:
189 # We do not apply any IDNA policy on decode.
190 if self.is_idna(label):
191 try:
192 slabel = label[4:].decode("punycode")
193 return _escapify(slabel)
194 except Exception as e:
195 raise IDNAException(idna_exception=e)
196 else:
197 return _escapify(label)
200class IDNA2003Codec(IDNACodec):
201 """IDNA 2003 encoder/decoder."""
203 def __init__(self, strict_decode: bool = False):
204 """Initialize the IDNA 2003 encoder/decoder.
206 *strict_decode* is a ``bool``. If `True`, then IDNA2003 checking
207 is done when decoding. This can cause failures if the name
208 was encoded with IDNA2008. The default is `False`.
209 """
211 super().__init__()
212 self.strict_decode = strict_decode
214 def encode(self, label: str) -> bytes:
215 """Encode *label*."""
217 if label == "":
218 return b""
219 try:
220 return encodings.idna.ToASCII(label)
221 except UnicodeError:
222 raise LabelTooLong
224 def decode(self, label: bytes) -> str:
225 """Decode *label*."""
226 if not self.strict_decode:
227 return super().decode(label)
228 if label == b"":
229 return ""
230 try:
231 return _escapify(encodings.idna.ToUnicode(label))
232 except Exception as e:
233 raise IDNAException(idna_exception=e)
236class IDNA2008Codec(IDNACodec):
237 """IDNA 2008 encoder/decoder."""
239 def __init__(
240 self,
241 uts_46: bool = False,
242 transitional: bool = False,
243 allow_pure_ascii: bool = False,
244 strict_decode: bool = False,
245 ):
246 """Initialize the IDNA 2008 encoder/decoder.
248 *uts_46* is a ``bool``. If True, apply Unicode IDNA
249 compatibility processing as described in Unicode Technical
250 Standard #46 (https://unicode.org/reports/tr46/).
251 If False, do not apply the mapping. The default is False.
253 *transitional* is a ``bool``: If True, use the
254 "transitional" mode described in Unicode Technical Standard
255 #46. The default is False.
257 *allow_pure_ascii* is a ``bool``. If True, then a label which
258 consists of only ASCII characters is allowed. This is less
259 strict than regular IDNA 2008, but is also necessary for mixed
260 names, e.g. a name with starting with "_sip._tcp." and ending
261 in an IDN suffix which would otherwise be disallowed. The
262 default is False.
264 *strict_decode* is a ``bool``: If True, then IDNA2008 checking
265 is done when decoding. This can cause failures if the name
266 was encoded with IDNA2003. The default is False.
267 """
268 super().__init__()
269 self.uts_46 = uts_46
270 self.transitional = transitional
271 self.allow_pure_ascii = allow_pure_ascii
272 self.strict_decode = strict_decode
274 def encode(self, label: str) -> bytes:
275 if label == "":
276 return b""
277 if self.allow_pure_ascii and is_all_ascii(label):
278 encoded = label.encode("ascii")
279 if len(encoded) > 63:
280 raise LabelTooLong
281 return encoded
282 if not have_idna_2008:
283 raise NoIDNA2008
284 try:
285 if self.uts_46:
286 # pylint: disable=possibly-used-before-assignment
287 label = idna.uts46_remap(label, False, self.transitional)
288 return idna.alabel(label)
289 except idna.IDNAError as e:
290 if e.args[0] == "Label too long":
291 raise LabelTooLong
292 else:
293 raise IDNAException(idna_exception=e)
295 def decode(self, label: bytes) -> str:
296 if not self.strict_decode:
297 return super().decode(label)
298 if label == b"":
299 return ""
300 if not have_idna_2008:
301 raise NoIDNA2008
302 try:
303 ulabel = idna.ulabel(label)
304 if self.uts_46:
305 ulabel = idna.uts46_remap(ulabel, False, self.transitional)
306 return _escapify(ulabel)
307 except (idna.IDNAError, UnicodeError) as e:
308 raise IDNAException(idna_exception=e)
311IDNA_2003_Practical = IDNA2003Codec(False)
312IDNA_2003_Strict = IDNA2003Codec(True)
313IDNA_2003 = IDNA_2003_Practical
314IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)
315IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)
316IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)
317IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)
318IDNA_2008 = IDNA_2008_Practical
321def _validate_labels(labels: tuple[bytes, ...]) -> None:
322 """Check for empty labels in the middle of a label sequence,
323 labels that are too long, and for too many labels.
325 Raises ``dns.name.NameTooLong`` if the name as a whole is too long.
327 Raises ``dns.name.EmptyLabel`` if a label is empty (i.e. the root
328 label) and appears in a position other than the end of the label
329 sequence
331 """
333 l = len(labels)
334 total = 0
335 i = -1
336 j = 0
337 for label in labels:
338 ll = len(label)
339 total += ll + 1
340 if ll > 63:
341 raise LabelTooLong
342 if i < 0 and label == b"":
343 i = j
344 j += 1
345 if total > 255:
346 raise NameTooLong
347 if i >= 0 and i != l - 1:
348 raise EmptyLabel
351def _maybe_convert_to_binary(label: bytes | str) -> bytes:
352 """If label is ``str``, convert it to ``bytes``. If it is already
353 ``bytes`` just return it.
355 """
357 if isinstance(label, bytes):
358 return label
359 else:
360 return label.encode()
363@dns.immutable.immutable
364class Name:
365 """A DNS name.
367 The dns.name.Name class represents a DNS name as a tuple of
368 labels. Each label is a ``bytes`` in DNS wire format. Instances
369 of the class are immutable.
370 """
372 __slots__ = ["labels"]
374 def __init__(self, labels: Iterable[bytes | str]):
375 """*labels* is any iterable whose values are ``str`` or ``bytes``."""
377 blabels = [_maybe_convert_to_binary(x) for x in labels]
378 self.labels = tuple(blabels)
379 _validate_labels(self.labels)
381 def __copy__(self):
382 return Name(self.labels)
384 def __deepcopy__(self, memo):
385 return Name(copy.deepcopy(self.labels, memo))
387 def __getstate__(self):
388 # Names can be pickled
389 return {"labels": self.labels}
391 def __setstate__(self, state):
392 super().__setattr__("labels", state["labels"])
393 _validate_labels(self.labels)
395 def is_absolute(self) -> bool:
396 """Is the most significant label of this name the root label?
398 Returns a ``bool``.
399 """
401 return len(self.labels) > 0 and self.labels[-1] == b""
403 def is_wild(self) -> bool:
404 """Is this name wild? (I.e. Is the least significant label '*'?)
406 Returns a ``bool``.
407 """
409 return len(self.labels) > 0 and self.labels[0] == b"*"
411 def __hash__(self) -> int:
412 """Return a case-insensitive hash of the name.
414 Returns an ``int``.
415 """
417 h = 0
418 for label in self.labels:
419 for c in label.lower():
420 h += (h << 3) + c
421 return h
423 def fullcompare(self, other: "Name") -> tuple[NameRelation, int, int]:
424 """Compare two names, returning a 3-tuple
425 ``(relation, order, nlabels)``.
427 *relation* describes the relation ship between the names,
428 and is one of: ``dns.name.NameRelation.NONE``,
429 ``dns.name.NameRelation.SUPERDOMAIN``, ``dns.name.NameRelation.SUBDOMAIN``,
430 ``dns.name.NameRelation.EQUAL``, or ``dns.name.NameRelation.COMMONANCESTOR``.
432 *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==
433 0 if *self* == *other*. A relative name is always less than an
434 absolute name. If both names have the same relativity, then
435 the DNSSEC order relation is used to order them.
437 *nlabels* is the number of significant labels that the two names
438 have in common.
440 Here are some examples. Names ending in "." are absolute names,
441 those not ending in "." are relative names.
443 ============= ============= =========== ===== =======
444 self other relation order nlabels
445 ============= ============= =========== ===== =======
446 www.example. www.example. equal 0 3
447 www.example. example. subdomain > 0 2
448 example. www.example. superdomain < 0 2
449 example1.com. example2.com. common anc. < 0 2
450 example1 example2. none < 0 0
451 example1. example2 none > 0 0
452 ============= ============= =========== ===== =======
453 """
455 sabs = self.is_absolute()
456 oabs = other.is_absolute()
457 if sabs != oabs:
458 if sabs:
459 return (NameRelation.NONE, 1, 0)
460 else:
461 return (NameRelation.NONE, -1, 0)
462 l1 = len(self.labels)
463 l2 = len(other.labels)
464 ldiff = l1 - l2
465 if ldiff < 0:
466 l = l1
467 else:
468 l = l2
470 order = 0
471 nlabels = 0
472 namereln = NameRelation.NONE
473 while l > 0:
474 l -= 1
475 l1 -= 1
476 l2 -= 1
477 label1 = self.labels[l1].lower()
478 label2 = other.labels[l2].lower()
479 if label1 < label2:
480 order = -1
481 if nlabels > 0:
482 namereln = NameRelation.COMMONANCESTOR
483 return (namereln, order, nlabels)
484 elif label1 > label2:
485 order = 1
486 if nlabels > 0:
487 namereln = NameRelation.COMMONANCESTOR
488 return (namereln, order, nlabels)
489 nlabels += 1
490 order = ldiff
491 if ldiff < 0:
492 namereln = NameRelation.SUPERDOMAIN
493 elif ldiff > 0:
494 namereln = NameRelation.SUBDOMAIN
495 else:
496 namereln = NameRelation.EQUAL
497 return (namereln, order, nlabels)
499 def is_subdomain(self, other: "Name") -> bool:
500 """Is self a subdomain of other?
502 Note that the notion of subdomain includes equality, e.g.
503 "dnspython.org" is a subdomain of itself.
505 Returns a ``bool``.
506 """
508 (nr, _, _) = self.fullcompare(other)
509 if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL:
510 return True
511 return False
513 def is_superdomain(self, other: "Name") -> bool:
514 """Is self a superdomain of other?
516 Note that the notion of superdomain includes equality, e.g.
517 "dnspython.org" is a superdomain of itself.
519 Returns a ``bool``.
520 """
522 (nr, _, _) = self.fullcompare(other)
523 if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL:
524 return True
525 return False
527 def canonicalize(self) -> "Name":
528 """Return a name which is equal to the current name, but is in
529 DNSSEC canonical form.
530 """
532 return Name([x.lower() for x in self.labels])
534 def __eq__(self, other):
535 if isinstance(other, Name):
536 return self.fullcompare(other)[1] == 0
537 else:
538 return False
540 def __ne__(self, other):
541 if isinstance(other, Name):
542 return self.fullcompare(other)[1] != 0
543 else:
544 return True
546 def __lt__(self, other):
547 if isinstance(other, Name):
548 return self.fullcompare(other)[1] < 0
549 else:
550 return NotImplemented
552 def __le__(self, other):
553 if isinstance(other, Name):
554 return self.fullcompare(other)[1] <= 0
555 else:
556 return NotImplemented
558 def __ge__(self, other):
559 if isinstance(other, Name):
560 return self.fullcompare(other)[1] >= 0
561 else:
562 return NotImplemented
564 def __gt__(self, other):
565 if isinstance(other, Name):
566 return self.fullcompare(other)[1] > 0
567 else:
568 return NotImplemented
570 def __repr__(self):
571 return "<DNS name " + self.__str__() + ">"
573 def __str__(self):
574 return self.to_text(False)
576 def to_text(self, omit_final_dot: bool = False) -> str:
577 """Convert name to DNS text format.
579 *omit_final_dot* is a ``bool``. If True, don't emit the final
580 dot (denoting the root label) for absolute names. The default
581 is False.
583 Returns a ``str``.
584 """
586 if len(self.labels) == 0:
587 return "@"
588 if len(self.labels) == 1 and self.labels[0] == b"":
589 return "."
590 if omit_final_dot and self.is_absolute():
591 l = self.labels[:-1]
592 else:
593 l = self.labels
594 s = ".".join(map(_escapify, l))
595 return s
597 def to_unicode(
598 self, omit_final_dot: bool = False, idna_codec: IDNACodec | None = None
599 ) -> str:
600 """Convert name to Unicode text format.
602 IDN ACE labels are converted to Unicode.
604 *omit_final_dot* is a ``bool``. If True, don't emit the final
605 dot (denoting the root label) for absolute names. The default
606 is False.
607 *idna_codec* specifies the IDNA encoder/decoder. If None, the
608 dns.name.IDNA_2003_Practical encoder/decoder is used.
609 The IDNA_2003_Practical decoder does
610 not impose any policy, it just decodes punycode, so if you
611 don't want checking for compliance, you can use this decoder
612 for IDNA2008 as well.
614 Returns a ``str``.
615 """
617 if len(self.labels) == 0:
618 return "@"
619 if len(self.labels) == 1 and self.labels[0] == b"":
620 return "."
621 if omit_final_dot and self.is_absolute():
622 l = self.labels[:-1]
623 else:
624 l = self.labels
625 if idna_codec is None:
626 idna_codec = IDNA_2003_Practical
627 return ".".join([idna_codec.decode(x) for x in l])
629 def to_digestable(self, origin: "Name | None" = None) -> bytes:
630 """Convert name to a format suitable for digesting in hashes.
632 The name is canonicalized and converted to uncompressed wire
633 format. All names in wire format are absolute. If the name
634 is a relative name, then an origin must be supplied.
636 *origin* is a ``dns.name.Name`` or ``None``. If the name is
637 relative and origin is not ``None``, then origin will be appended
638 to the name.
640 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
641 relative and no origin was provided.
643 Returns a ``bytes``.
644 """
646 digest = self.to_wire(origin=origin, canonicalize=True)
647 assert digest is not None
648 return digest
650 def to_wire(
651 self,
652 file: Any | None = None,
653 compress: CompressType | None = None,
654 origin: "Name | None" = None,
655 canonicalize: bool = False,
656 ) -> bytes | None:
657 """Convert name to wire format, possibly compressing it.
659 *file* is the file where the name is emitted (typically an
660 io.BytesIO file). If ``None`` (the default), a ``bytes``
661 containing the wire name will be returned.
663 *compress*, a ``dict``, is the compression table to use. If
664 ``None`` (the default), names will not be compressed. Note that
665 the compression code assumes that compression offset 0 is the
666 start of *file*, and thus compression will not be correct
667 if this is not the case.
669 *origin* is a ``dns.name.Name`` or ``None``. If the name is
670 relative and origin is not ``None``, then *origin* will be appended
671 to it.
673 *canonicalize*, a ``bool``, indicates whether the name should
674 be canonicalized; that is, converted to a format suitable for
675 digesting in hashes.
677 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
678 relative and no origin was provided.
680 Returns a ``bytes`` or ``None``.
681 """
683 if file is None:
684 out = bytearray()
685 for label in self.labels:
686 out.append(len(label))
687 if canonicalize:
688 out += label.lower()
689 else:
690 out += label
691 if not self.is_absolute():
692 if origin is None or not origin.is_absolute():
693 raise NeedAbsoluteNameOrOrigin
694 for label in origin.labels:
695 out.append(len(label))
696 if canonicalize:
697 out += label.lower()
698 else:
699 out += label
700 return bytes(out)
702 labels: Iterable[bytes]
703 if not self.is_absolute():
704 if origin is None or not origin.is_absolute():
705 raise NeedAbsoluteNameOrOrigin
706 labels = list(self.labels)
707 labels.extend(list(origin.labels))
708 else:
709 labels = self.labels
710 i = 0
711 for label in labels:
712 n = Name(labels[i:])
713 i += 1
714 if compress is not None:
715 pos = compress.get(n)
716 else:
717 pos = None
718 if pos is not None:
719 value = 0xC000 + pos
720 s = struct.pack("!H", value)
721 file.write(s)
722 break
723 else:
724 if compress is not None and len(n) > 1:
725 pos = file.tell()
726 if pos <= 0x3FFF:
727 compress[n] = pos
728 l = len(label)
729 file.write(struct.pack("!B", l))
730 if l > 0:
731 if canonicalize:
732 file.write(label.lower())
733 else:
734 file.write(label)
735 return None
737 def __len__(self) -> int:
738 """The length of the name (in labels).
740 Returns an ``int``.
741 """
743 return len(self.labels)
745 def __getitem__(self, index: Any) -> Any:
746 return self.labels[index]
748 def __add__(self, other):
749 return self.concatenate(other)
751 def __sub__(self, other):
752 return self.relativize(other)
754 def split(self, depth: int) -> tuple["Name", "Name"]:
755 """Split a name into a prefix and suffix names at the specified depth.
757 *depth* is an ``int`` specifying the number of labels in the suffix
759 Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the
760 name.
762 Returns the tuple ``(prefix, suffix)``.
763 """
765 l = len(self.labels)
766 if depth == 0:
767 return (self, empty)
768 elif depth == l:
769 return (empty, self)
770 elif depth < 0 or depth > l:
771 raise ValueError("depth must be >= 0 and <= the length of the name")
772 return (Name(self[:-depth]), Name(self[-depth:]))
774 def concatenate(self, other: "Name") -> "Name":
775 """Return a new name which is the concatenation of self and other.
777 Raises ``dns.name.AbsoluteConcatenation`` if the name is
778 absolute and *other* is not the empty name.
780 Returns a ``dns.name.Name``.
781 """
783 if self.is_absolute() and len(other) > 0:
784 raise AbsoluteConcatenation
785 labels = list(self.labels)
786 labels.extend(list(other.labels))
787 return Name(labels)
789 def relativize(self, origin: "Name") -> "Name":
790 """If the name is a subdomain of *origin*, return a new name which is
791 the name relative to origin. Otherwise return the name.
793 For example, relativizing ``www.dnspython.org.`` to origin
794 ``dnspython.org.`` returns the name ``www``. Relativizing ``example.``
795 to origin ``dnspython.org.`` returns ``example.``.
797 Returns a ``dns.name.Name``.
798 """
800 if self.is_subdomain(origin):
801 return Name(self[: -len(origin)])
802 else:
803 return self
805 def derelativize(self, origin: "Name") -> "Name":
806 """If the name is a relative name, return a new name which is the
807 concatenation of the name and origin. Otherwise return the name.
809 For example, derelativizing ``www`` to origin ``dnspython.org.``
810 returns the name ``www.dnspython.org.``. Derelativizing ``example.``
811 to origin ``dnspython.org.`` returns ``example.``.
813 Returns a ``dns.name.Name``.
814 """
816 if not self.is_absolute():
817 return self.concatenate(origin)
818 else:
819 return self
821 def choose_relativity(
822 self, origin: "Name | None" = None, relativize: bool = True
823 ) -> "Name":
824 """Return a name with the relativity desired by the caller.
826 If *origin* is ``None``, then the name is returned.
827 Otherwise, if *relativize* is ``True`` the name is
828 relativized, and if *relativize* is ``False`` the name is
829 derelativized.
831 Returns a ``dns.name.Name``.
832 """
834 if origin:
835 if relativize:
836 return self.relativize(origin)
837 else:
838 return self.derelativize(origin)
839 else:
840 return self
842 def parent(self) -> "Name":
843 """Return the parent of the name.
845 For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``.
847 Raises ``dns.name.NoParent`` if the name is either the root name or the
848 empty name, and thus has no parent.
850 Returns a ``dns.name.Name``.
851 """
853 if self == root or self == empty:
854 raise NoParent
855 return Name(self.labels[1:])
857 def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
858 """Return the maximal predecessor of *name* in the DNSSEC ordering in the zone
859 whose origin is *origin*, or return the longest name under *origin* if the
860 name is origin (i.e. wrap around to the longest name, which may still be
861 *origin* due to length considerations.
863 The relativity of the name is preserved, so if this name is relative
864 then the method will return a relative name, and likewise if this name
865 is absolute then the predecessor will be absolute.
867 *prefix_ok* indicates if prefixing labels is allowed, and
868 defaults to ``True``. Normally it is good to allow this, but if computing
869 a maximal predecessor at a zone cut point then ``False`` must be specified.
870 """
871 return _handle_relativity_and_call(
872 _absolute_predecessor, self, origin, prefix_ok
873 )
875 def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
876 """Return the minimal successor of *name* in the DNSSEC ordering in the zone
877 whose origin is *origin*, or return *origin* if the successor cannot be
878 computed due to name length limitations.
880 Note that *origin* is returned in the "too long" cases because wrapping
881 around to the origin is how NSEC records express "end of the zone".
883 The relativity of the name is preserved, so if this name is relative
884 then the method will return a relative name, and likewise if this name
885 is absolute then the successor will be absolute.
887 *prefix_ok* indicates if prefixing a new minimal label is allowed, and
888 defaults to ``True``. Normally it is good to allow this, but if computing
889 a minimal successor at a zone cut point then ``False`` must be specified.
890 """
891 return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok)
894#: The root name, '.'
895root = Name([b""])
897#: The empty name.
898empty = Name([])
901def from_unicode(
902 text: str, origin: Name | None = root, idna_codec: IDNACodec | None = None
903) -> Name:
904 """Convert unicode text into a Name object.
906 Labels are encoded in IDN ACE form according to rules specified by
907 the IDNA codec.
909 *text*, a ``str``, is the text to convert into a name.
911 *origin*, a ``dns.name.Name``, specifies the origin to
912 append to non-absolute names. The default is the root name.
914 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
915 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
916 is used.
918 Returns a ``dns.name.Name``.
919 """
921 labels = []
922 label = ""
923 escaping = False
924 edigits = 0
925 total = 0
926 if idna_codec is None:
927 idna_codec = IDNA_2003
928 if text == "@":
929 text = ""
930 if text:
931 if text in [".", "\u3002", "\uff0e", "\uff61"]:
932 return Name([b""]) # no Unicode "u" on this constant!
933 for c in text:
934 if escaping:
935 if edigits == 0:
936 if c.isdigit():
937 total = int(c)
938 edigits += 1
939 else:
940 label += c
941 escaping = False
942 else:
943 if not c.isdigit():
944 raise BadEscape
945 total *= 10
946 total += int(c)
947 edigits += 1
948 if edigits == 3:
949 escaping = False
950 label += chr(total)
951 elif c in [".", "\u3002", "\uff0e", "\uff61"]:
952 if len(label) == 0:
953 raise EmptyLabel
954 labels.append(idna_codec.encode(label))
955 label = ""
956 elif c == "\\":
957 escaping = True
958 edigits = 0
959 total = 0
960 else:
961 label += c
962 if escaping:
963 raise BadEscape
964 if len(label) > 0:
965 labels.append(idna_codec.encode(label))
966 else:
967 labels.append(b"")
969 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
970 labels.extend(list(origin.labels))
971 return Name(labels)
974def is_all_ascii(text: str) -> bool:
975 for c in text:
976 if ord(c) > 0x7F:
977 return False
978 return True
981def from_text(
982 text: bytes | str,
983 origin: Name | None = root,
984 idna_codec: IDNACodec | None = None,
985) -> Name:
986 """Convert text into a Name object.
988 *text*, a ``bytes`` or ``str``, is the text to convert into a name.
990 *origin*, a ``dns.name.Name``, specifies the origin to
991 append to non-absolute names. The default is the root name.
993 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
994 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
995 is used.
997 Returns a ``dns.name.Name``.
998 """
1000 if isinstance(text, str):
1001 if not is_all_ascii(text):
1002 # Some codepoint in the input text is > 127, so IDNA applies.
1003 return from_unicode(text, origin, idna_codec)
1004 # The input is all ASCII, so treat this like an ordinary non-IDNA
1005 # domain name. Note that "all ASCII" is about the input text,
1006 # not the codepoints in the domain name. E.g. if text has value
1007 #
1008 # r'\150\151\152\153\154\155\156\157\158\159'
1009 #
1010 # then it's still "all ASCII" even though the domain name has
1011 # codepoints > 127.
1012 text = text.encode("ascii")
1013 labels = []
1014 label = b""
1015 escaping = False
1016 edigits = 0
1017 total = 0
1018 if text == b"@":
1019 text = b""
1020 if text:
1021 if text == b".":
1022 return Name([b""])
1023 for c in text:
1024 byte_ = struct.pack("!B", c)
1025 if escaping:
1026 if edigits == 0:
1027 if byte_.isdigit():
1028 total = int(byte_)
1029 edigits += 1
1030 else:
1031 label += byte_
1032 escaping = False
1033 else:
1034 if not byte_.isdigit():
1035 raise BadEscape
1036 total *= 10
1037 total += int(byte_)
1038 edigits += 1
1039 if edigits == 3:
1040 escaping = False
1041 label += struct.pack("!B", total)
1042 elif byte_ == b".":
1043 if len(label) == 0:
1044 raise EmptyLabel
1045 labels.append(label)
1046 label = b""
1047 elif byte_ == b"\\":
1048 escaping = True
1049 edigits = 0
1050 total = 0
1051 else:
1052 label += byte_
1053 if escaping:
1054 raise BadEscape
1055 if len(label) > 0:
1056 labels.append(label)
1057 else:
1058 labels.append(b"")
1059 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
1060 labels.extend(list(origin.labels))
1061 return Name(labels)
1064def from_wire_parser(parser: dns.wirebase.Parser) -> Name:
1065 """Convert possibly compressed wire format into a Name.
1067 *parser* is a dns.wirebase.Parser.
1069 Raises ``dns.name.BadPointer`` if a compression pointer did not
1070 point backwards in the message.
1072 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
1074 Returns a ``dns.name.Name``
1075 """
1077 labels = []
1078 biggest_pointer = parser.current
1079 with parser.restore_furthest():
1080 count = parser.get_uint8()
1081 while count != 0:
1082 if count < 64:
1083 labels.append(parser.get_bytes(count))
1084 elif count >= 192:
1085 current = (count & 0x3F) * 256 + parser.get_uint8()
1086 if current >= biggest_pointer:
1087 raise BadPointer
1088 biggest_pointer = current
1089 parser.seek(current)
1090 else:
1091 raise BadLabelType
1092 count = parser.get_uint8()
1093 labels.append(b"")
1094 return Name(labels)
1097def from_wire(message: bytes, current: int) -> tuple[Name, int]:
1098 """Convert possibly compressed wire format into a Name.
1100 *message* is a ``bytes`` containing an entire DNS message in DNS
1101 wire form.
1103 *current*, an ``int``, is the offset of the beginning of the name
1104 from the start of the message
1106 Raises ``dns.name.BadPointer`` if a compression pointer did not
1107 point backwards in the message.
1109 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
1111 Returns a ``(dns.name.Name, int)`` tuple consisting of the name
1112 that was read and the number of bytes of the wire format message
1113 which were consumed reading it.
1114 """
1116 parser = dns.wirebase.Parser(message, current)
1117 name = from_wire_parser(parser)
1118 return (name, parser.current - current)
1121# RFC 4471 Support
1123_MINIMAL_OCTET = b"\x00"
1124_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET)
1125_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET])
1126_MAXIMAL_OCTET = b"\xff"
1127_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET)
1128_AT_SIGN_VALUE = ord("@")
1129_LEFT_SQUARE_BRACKET_VALUE = ord("[")
1132def _wire_length(labels):
1133 return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0)
1136def _pad_to_max_name(name):
1137 needed = 255 - _wire_length(name.labels)
1138 new_labels = []
1139 while needed > 64:
1140 new_labels.append(_MAXIMAL_OCTET * 63)
1141 needed -= 64
1142 if needed >= 2:
1143 new_labels.append(_MAXIMAL_OCTET * (needed - 1))
1144 # Note we're already maximal in the needed == 1 case as while we'd like
1145 # to add one more byte as a new label, we can't, as adding a new non-empty
1146 # label requires at least 2 bytes.
1147 new_labels = list(reversed(new_labels))
1148 new_labels.extend(name.labels)
1149 return Name(new_labels)
1152def _pad_to_max_label(label, suffix_labels):
1153 length = len(label)
1154 # We have to subtract one here to account for the length byte of label.
1155 remaining = 255 - _wire_length(suffix_labels) - length - 1
1156 if remaining <= 0:
1157 # Shouldn't happen!
1158 return label
1159 needed = min(63 - length, remaining)
1160 return label + _MAXIMAL_OCTET * needed
1163def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1164 # This is the RFC 4471 predecessor algorithm using the "absolute method" of section
1165 # 3.1.1.
1166 #
1167 # Our caller must ensure that the name and origin are absolute, and that name is a
1168 # subdomain of origin.
1169 if name == origin:
1170 return _pad_to_max_name(name)
1171 least_significant_label = name[0]
1172 if least_significant_label == _MINIMAL_OCTET:
1173 return name.parent()
1174 least_octet = least_significant_label[-1]
1175 suffix_labels = name.labels[1:]
1176 if least_octet == _MINIMAL_OCTET_VALUE:
1177 new_labels = [least_significant_label[:-1]]
1178 else:
1179 octets = bytearray(least_significant_label)
1180 octet = octets[-1]
1181 if octet == _LEFT_SQUARE_BRACKET_VALUE:
1182 octet = _AT_SIGN_VALUE
1183 else:
1184 octet -= 1
1185 octets[-1] = octet
1186 least_significant_label = bytes(octets)
1187 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)]
1188 new_labels.extend(suffix_labels)
1189 name = Name(new_labels)
1190 if prefix_ok:
1191 return _pad_to_max_name(name)
1192 else:
1193 return name
1196def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1197 # This is the RFC 4471 successor algorithm using the "absolute method" of section
1198 # 3.1.2.
1199 #
1200 # Our caller must ensure that the name and origin are absolute, and that name is a
1201 # subdomain of origin.
1202 if prefix_ok:
1203 # Try prefixing \000 as new label
1204 try:
1205 return _SUCCESSOR_PREFIX.concatenate(name)
1206 except NameTooLong:
1207 pass
1208 while name != origin:
1209 # Try extending the least significant label.
1210 least_significant_label = name[0]
1211 if len(least_significant_label) < 63:
1212 # We may be able to extend the least label with a minimal additional byte.
1213 # This is only "may" because we could have a maximal length name even though
1214 # the least significant label isn't maximally long.
1215 new_labels = [least_significant_label + _MINIMAL_OCTET]
1216 new_labels.extend(name.labels[1:])
1217 try:
1218 return Name(new_labels)
1219 except NameTooLong:
1220 pass
1221 # We can't extend the label either, so we'll try to increment the least
1222 # signficant non-maximal byte in it.
1223 octets = bytearray(least_significant_label)
1224 # We do this reversed iteration with an explicit indexing variable because
1225 # if we find something to increment, we're going to want to truncate everything
1226 # to the right of it.
1227 for i in range(len(octets) - 1, -1, -1):
1228 octet = octets[i]
1229 if octet == _MAXIMAL_OCTET_VALUE:
1230 # We can't increment this, so keep looking.
1231 continue
1232 # Finally, something we can increment. We have to apply a special rule for
1233 # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when
1234 # comparing names, uppercase letters compare as if they were their
1235 # lower-case equivalents. If we increment "@" to "A", then it would compare
1236 # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have
1237 # skipped the most minimal successor, namely "[".
1238 if octet == _AT_SIGN_VALUE:
1239 octet = _LEFT_SQUARE_BRACKET_VALUE
1240 else:
1241 octet += 1
1242 octets[i] = octet
1243 # We can now truncate all of the maximal values we skipped (if any)
1244 new_labels = [bytes(octets[: i + 1])]
1245 new_labels.extend(name.labels[1:])
1246 # We haven't changed the length of the name, so the Name constructor will
1247 # always work.
1248 return Name(new_labels)
1249 # We couldn't increment, so chop off the least significant label and try
1250 # again.
1251 name = name.parent()
1253 # We couldn't increment at all, so return the origin, as wrapping around is the
1254 # DNSSEC way.
1255 return origin
1258def _handle_relativity_and_call(
1259 function: Callable[[Name, Name, bool], Name],
1260 name: Name,
1261 origin: Name,
1262 prefix_ok: bool,
1263) -> Name:
1264 # Make "name" absolute if needed, ensure that the origin is absolute,
1265 # call function(), and then relativize the result if needed.
1266 if not origin.is_absolute():
1267 raise NeedAbsoluteNameOrOrigin
1268 relative = not name.is_absolute()
1269 if relative:
1270 name = name.derelativize(origin)
1271 elif not name.is_subdomain(origin):
1272 raise NeedSubdomainOfOrigin
1273 result_name = function(name, origin, prefix_ok)
1274 if relative:
1275 result_name = result_name.relativize(origin)
1276 return result_name