Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dns/name.py: 26%
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 # pyright: 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 # pyright: 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. This setting has no effect
256 in idna 3.11 and later as transitional support has been removed.
258 *allow_pure_ascii* is a ``bool``. If True, then a label which
259 consists of only ASCII characters is allowed. This is less
260 strict than regular IDNA 2008, but is also necessary for mixed
261 names, e.g. a name with starting with "_sip._tcp." and ending
262 in an IDN suffix which would otherwise be disallowed. The
263 default is False.
265 *strict_decode* is a ``bool``: If True, then IDNA2008 checking
266 is done when decoding. This can cause failures if the name
267 was encoded with IDNA2003. The default is False.
268 """
269 super().__init__()
270 self.uts_46 = uts_46
271 self.transitional = transitional
272 self.allow_pure_ascii = allow_pure_ascii
273 self.strict_decode = strict_decode
275 def encode(self, label: str) -> bytes:
276 if label == "":
277 return b""
278 if self.allow_pure_ascii and is_all_ascii(label):
279 encoded = label.encode("ascii")
280 if len(encoded) > 63:
281 raise LabelTooLong
282 return encoded
283 if not have_idna_2008:
284 raise NoIDNA2008
285 try:
286 if self.uts_46:
287 # pylint: disable=possibly-used-before-assignment
288 label = idna.uts46_remap(label, False, self.transitional)
289 return idna.alabel(label)
290 except idna.IDNAError as e:
291 if e.args[0] == "Label too long":
292 raise LabelTooLong
293 else:
294 raise IDNAException(idna_exception=e)
296 def decode(self, label: bytes) -> str:
297 if not self.strict_decode:
298 return super().decode(label)
299 if label == b"":
300 return ""
301 if not have_idna_2008:
302 raise NoIDNA2008
303 try:
304 ulabel = idna.ulabel(label)
305 if self.uts_46:
306 ulabel = idna.uts46_remap(ulabel, False, self.transitional)
307 return _escapify(ulabel)
308 except (idna.IDNAError, UnicodeError) as e:
309 raise IDNAException(idna_exception=e)
312IDNA_2003_Practical = IDNA2003Codec(False)
313IDNA_2003_Strict = IDNA2003Codec(True)
314IDNA_2003 = IDNA_2003_Practical
315IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)
316IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)
317IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)
318IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)
319IDNA_2008 = IDNA_2008_Practical
320if have_idna_2008:
321 IDNA_DEFAULT = IDNA_2008_Practical
322else:
323 IDNA_DEFAULT = IDNA_2003_Practical
326def set_default_idna_codec(idna_codec: IDNACodec):
327 """Set the default IDNA codec."""
328 global IDNA_DEFAULT
329 IDNA_DEFAULT = idna_codec
332def _validate_labels(labels: tuple[bytes, ...]) -> None:
333 """Check for empty labels in the middle of a label sequence,
334 labels that are too long, and for too many labels.
336 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
342 """
344 l = len(labels)
345 total = 0
346 i = -1
347 j = 0
348 for label in labels:
349 ll = len(label)
350 total += ll + 1
351 if ll > 63:
352 raise LabelTooLong
353 if i < 0 and label == b"":
354 i = j
355 j += 1
356 if total > 255:
357 raise NameTooLong
358 if i >= 0 and i != l - 1:
359 raise EmptyLabel
362def _maybe_convert_to_binary(label: bytes | str) -> bytes:
363 """If label is ``str``, convert it to ``bytes``. If it is already
364 ``bytes`` just return it.
366 """
368 if isinstance(label, bytes):
369 return label
370 else:
371 return label.encode()
374@dns.immutable.immutable
375class Name:
376 """A DNS name.
378 The dns.name.Name class represents a DNS name as a tuple of
379 labels. Each label is a ``bytes`` in DNS wire format. Instances
380 of the class are immutable.
381 """
383 __slots__ = ["labels"]
385 def __init__(self, labels: Iterable[bytes | str]):
386 """*labels* is any iterable whose values are ``str`` or ``bytes``."""
388 blabels = [_maybe_convert_to_binary(x) for x in labels]
389 self.labels = tuple(blabels)
390 _validate_labels(self.labels)
392 def __copy__(self):
393 return Name(self.labels)
395 def __deepcopy__(self, memo):
396 return Name(copy.deepcopy(self.labels, memo))
398 def __getstate__(self):
399 # Names can be pickled
400 return {"labels": self.labels}
402 def __setstate__(self, state):
403 super().__setattr__("labels", state["labels"])
404 _validate_labels(self.labels)
406 def is_absolute(self) -> bool:
407 """Is the most significant label of this name the root label?
409 Returns a ``bool``.
410 """
412 return len(self.labels) > 0 and self.labels[-1] == b""
414 def is_wild(self) -> bool:
415 """Is this name wild? (I.e. Is the least significant label '*'?)
417 Returns a ``bool``.
418 """
420 return len(self.labels) > 0 and self.labels[0] == b"*"
422 def __hash__(self) -> int:
423 """Return a case-insensitive hash of the name.
425 Returns an ``int``.
426 """
428 h = 0
429 for label in self.labels:
430 for c in label.lower():
431 h += (h << 3) + c
432 return h
434 def fullcompare(self, other: "Name") -> tuple[NameRelation, int, int]:
435 """Compare two names, returning a 3-tuple
436 ``(relation, order, nlabels)``.
438 *relation* describes the relation ship between the names,
439 and is one of: ``dns.name.NameRelation.NONE``,
440 ``dns.name.NameRelation.SUPERDOMAIN``, ``dns.name.NameRelation.SUBDOMAIN``,
441 ``dns.name.NameRelation.EQUAL``, or ``dns.name.NameRelation.COMMONANCESTOR``.
443 *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==
444 0 if *self* == *other*. A relative name is always less than an
445 absolute name. If both names have the same relativity, then
446 the DNSSEC order relation is used to order them.
448 *nlabels* is the number of significant labels that the two names
449 have in common.
451 Here are some examples. Names ending in "." are absolute names,
452 those not ending in "." are relative names.
454 ============= ============= =========== ===== =======
455 self other relation order nlabels
456 ============= ============= =========== ===== =======
457 www.example. www.example. equal 0 3
458 www.example. example. subdomain > 0 2
459 example. www.example. superdomain < 0 2
460 example1.com. example2.com. common anc. < 0 2
461 example1 example2. none < 0 0
462 example1. example2 none > 0 0
463 ============= ============= =========== ===== =======
464 """
466 sabs = self.is_absolute()
467 oabs = other.is_absolute()
468 if sabs != oabs:
469 if sabs:
470 return (NameRelation.NONE, 1, 0)
471 else:
472 return (NameRelation.NONE, -1, 0)
473 l1 = len(self.labels)
474 l2 = len(other.labels)
475 ldiff = l1 - l2
476 if ldiff < 0:
477 l = l1
478 else:
479 l = l2
481 order = 0
482 nlabels = 0
483 namereln = NameRelation.NONE
484 while l > 0:
485 l -= 1
486 l1 -= 1
487 l2 -= 1
488 label1 = self.labels[l1].lower()
489 label2 = other.labels[l2].lower()
490 if label1 < label2:
491 order = -1
492 if nlabels > 0:
493 namereln = NameRelation.COMMONANCESTOR
494 return (namereln, order, nlabels)
495 elif label1 > label2:
496 order = 1
497 if nlabels > 0:
498 namereln = NameRelation.COMMONANCESTOR
499 return (namereln, order, nlabels)
500 nlabels += 1
501 order = ldiff
502 if ldiff < 0:
503 namereln = NameRelation.SUPERDOMAIN
504 elif ldiff > 0:
505 namereln = NameRelation.SUBDOMAIN
506 else:
507 namereln = NameRelation.EQUAL
508 return (namereln, order, nlabels)
510 def is_subdomain(self, other: "Name") -> bool:
511 """Is self a subdomain of other?
513 Note that the notion of subdomain includes equality, e.g.
514 "dnspython.org" is a subdomain of itself.
516 Returns a ``bool``.
517 """
519 nr, _, _ = self.fullcompare(other)
520 if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL:
521 return True
522 return False
524 def is_superdomain(self, other: "Name") -> bool:
525 """Is self a superdomain of other?
527 Note that the notion of superdomain includes equality, e.g.
528 "dnspython.org" is a superdomain of itself.
530 Returns a ``bool``.
531 """
533 nr, _, _ = self.fullcompare(other)
534 if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL:
535 return True
536 return False
538 def canonicalize(self) -> "Name":
539 """Return a name which is equal to the current name, but is in
540 DNSSEC canonical form.
541 """
543 return Name([x.lower() for x in self.labels])
545 def __eq__(self, other):
546 if isinstance(other, Name):
547 return self.fullcompare(other)[1] == 0
548 else:
549 return False
551 def __ne__(self, other):
552 if isinstance(other, Name):
553 return self.fullcompare(other)[1] != 0
554 else:
555 return True
557 def __lt__(self, other):
558 if isinstance(other, Name):
559 return self.fullcompare(other)[1] < 0
560 else:
561 return NotImplemented
563 def __le__(self, other):
564 if isinstance(other, Name):
565 return self.fullcompare(other)[1] <= 0
566 else:
567 return NotImplemented
569 def __ge__(self, other):
570 if isinstance(other, Name):
571 return self.fullcompare(other)[1] >= 0
572 else:
573 return NotImplemented
575 def __gt__(self, other):
576 if isinstance(other, Name):
577 return self.fullcompare(other)[1] > 0
578 else:
579 return NotImplemented
581 def __repr__(self):
582 return "<DNS name " + self.__str__() + ">"
584 def __str__(self):
585 return self.to_text(False)
587 def to_text(self, omit_final_dot: bool = False) -> str:
588 """Convert name to DNS text format.
590 *omit_final_dot* is a ``bool``. If True, don't emit the final
591 dot (denoting the root label) for absolute names. The default
592 is False.
594 Returns a ``str``.
595 """
597 if len(self.labels) == 0:
598 return "@"
599 if len(self.labels) == 1 and self.labels[0] == b"":
600 return "."
601 if omit_final_dot and self.is_absolute():
602 l = self.labels[:-1]
603 else:
604 l = self.labels
605 s = ".".join(map(_escapify, l))
606 return s
608 def to_unicode(
609 self, omit_final_dot: bool = False, idna_codec: IDNACodec | None = None
610 ) -> str:
611 """Convert name to Unicode text format.
613 IDN ACE labels are converted to Unicode.
615 *omit_final_dot* is a ``bool``. If True, don't emit the final
616 dot (denoting the root label) for absolute names. The default
617 is False.
618 *idna_codec* specifies the IDNA encoder/decoder. If None, the
619 dns.name.IDNA_DEFAULT encoder/decoder is used.
621 Returns a ``str``.
622 """
624 if len(self.labels) == 0:
625 return "@"
626 if len(self.labels) == 1 and self.labels[0] == b"":
627 return "."
628 if omit_final_dot and self.is_absolute():
629 l = self.labels[:-1]
630 else:
631 l = self.labels
632 if idna_codec is None:
633 idna_codec = IDNA_DEFAULT
634 return ".".join([idna_codec.decode(x) for x in l])
636 def to_digestable(self, origin: "Name | None" = None) -> bytes:
637 """Convert name to a format suitable for digesting in hashes.
639 The name is canonicalized and converted to uncompressed wire
640 format. All names in wire format are absolute. If the name
641 is a relative name, then an origin must be supplied.
643 *origin* is a ``dns.name.Name`` or ``None``. If the name is
644 relative and origin is not ``None``, then origin will be appended
645 to the name.
647 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
648 relative and no origin was provided.
650 Returns a ``bytes``.
651 """
653 digest = self.to_wire(origin=origin, canonicalize=True)
654 assert digest is not None
655 return digest
657 def to_wire(
658 self,
659 file: Any | None = None,
660 compress: CompressType | None = None,
661 origin: "Name | None" = None,
662 canonicalize: bool = False,
663 ) -> bytes | None:
664 """Convert name to wire format, possibly compressing it.
666 *file* is the file where the name is emitted (typically an
667 io.BytesIO file). If ``None`` (the default), a ``bytes``
668 containing the wire name will be returned.
670 *compress*, a ``dict``, is the compression table to use. If
671 ``None`` (the default), names will not be compressed. Note that
672 the compression code assumes that compression offset 0 is the
673 start of *file*, and thus compression will not be correct
674 if this is not the case.
676 *origin* is a ``dns.name.Name`` or ``None``. If the name is
677 relative and origin is not ``None``, then *origin* will be appended
678 to it.
680 *canonicalize*, a ``bool``, indicates whether the name should
681 be canonicalized; that is, converted to a format suitable for
682 digesting in hashes.
684 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
685 relative and no origin was provided.
687 Returns a ``bytes`` or ``None``.
688 """
690 if file is None:
691 out = bytearray()
692 for label in self.labels:
693 out.append(len(label))
694 if canonicalize:
695 out += label.lower()
696 else:
697 out += label
698 if not self.is_absolute():
699 if origin is None or not origin.is_absolute():
700 raise NeedAbsoluteNameOrOrigin
701 for label in origin.labels:
702 out.append(len(label))
703 if canonicalize:
704 out += label.lower()
705 else:
706 out += label
707 return bytes(out)
709 labels: Iterable[bytes]
710 if not self.is_absolute():
711 if origin is None or not origin.is_absolute():
712 raise NeedAbsoluteNameOrOrigin
713 labels = list(self.labels)
714 labels.extend(list(origin.labels))
715 else:
716 labels = self.labels
717 i = 0
718 for label in labels:
719 n = Name(labels[i:])
720 i += 1
721 if compress is not None:
722 pos = compress.get(n)
723 else:
724 pos = None
725 if pos is not None:
726 value = 0xC000 + pos
727 s = struct.pack("!H", value)
728 file.write(s)
729 break
730 else:
731 if compress is not None and len(n) > 1:
732 pos = file.tell()
733 if pos <= 0x3FFF:
734 compress[n] = pos
735 l = len(label)
736 file.write(struct.pack("!B", l))
737 if l > 0:
738 if canonicalize:
739 file.write(label.lower())
740 else:
741 file.write(label)
742 return None
744 def __len__(self) -> int:
745 """The length of the name (in labels).
747 Returns an ``int``.
748 """
750 return len(self.labels)
752 def __getitem__(self, index: Any) -> Any:
753 return self.labels[index]
755 def __add__(self, other):
756 return self.concatenate(other)
758 def __sub__(self, other):
759 return self.relativize(other)
761 def split(self, depth: int) -> tuple["Name", "Name"]:
762 """Split a name into a prefix and suffix names at the specified depth.
764 *depth* is an ``int`` specifying the number of labels in the suffix
766 Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the
767 name.
769 Returns the tuple ``(prefix, suffix)``.
770 """
772 l = len(self.labels)
773 if depth == 0:
774 return (self, empty)
775 elif depth == l:
776 return (empty, self)
777 elif depth < 0 or depth > l:
778 raise ValueError("depth must be >= 0 and <= the length of the name")
779 return (Name(self[:-depth]), Name(self[-depth:]))
781 def concatenate(self, other: "Name") -> "Name":
782 """Return a new name which is the concatenation of self and other.
784 Raises ``dns.name.AbsoluteConcatenation`` if the name is
785 absolute and *other* is not the empty name.
787 Returns a ``dns.name.Name``.
788 """
790 if self.is_absolute() and len(other) > 0:
791 raise AbsoluteConcatenation
792 labels = list(self.labels)
793 labels.extend(list(other.labels))
794 return Name(labels)
796 def relativize(self, origin: "Name") -> "Name":
797 """If the name is a subdomain of *origin*, return a new name which is
798 the name relative to origin. Otherwise return the name.
800 For example, relativizing ``www.dnspython.org.`` to origin
801 ``dnspython.org.`` returns the name ``www``. Relativizing ``example.``
802 to origin ``dnspython.org.`` returns ``example.``.
804 Returns a ``dns.name.Name``.
805 """
807 if self.is_subdomain(origin):
808 return Name(self[: -len(origin)])
809 else:
810 return self
812 def derelativize(self, origin: "Name") -> "Name":
813 """If the name is a relative name, return a new name which is the
814 concatenation of the name and origin. Otherwise return the name.
816 For example, derelativizing ``www`` to origin ``dnspython.org.``
817 returns the name ``www.dnspython.org.``. Derelativizing ``example.``
818 to origin ``dnspython.org.`` returns ``example.``.
820 Returns a ``dns.name.Name``.
821 """
823 if not self.is_absolute():
824 return self.concatenate(origin)
825 else:
826 return self
828 def choose_relativity(
829 self, origin: "Name | None" = None, relativize: bool = True
830 ) -> "Name":
831 """Return a name with the relativity desired by the caller.
833 If *origin* is ``None``, then the name is returned.
834 Otherwise, if *relativize* is ``True`` the name is
835 relativized, and if *relativize* is ``False`` the name is
836 derelativized.
838 Returns a ``dns.name.Name``.
839 """
841 if origin:
842 if relativize:
843 return self.relativize(origin)
844 else:
845 return self.derelativize(origin)
846 else:
847 return self
849 def parent(self) -> "Name":
850 """Return the parent of the name.
852 For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``.
854 Raises ``dns.name.NoParent`` if the name is either the root name or the
855 empty name, and thus has no parent.
857 Returns a ``dns.name.Name``.
858 """
860 if self == root or self == empty:
861 raise NoParent
862 return Name(self.labels[1:])
864 def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
865 """Return the maximal predecessor of *name* in the DNSSEC ordering in the zone
866 whose origin is *origin*, or return the longest name under *origin* if the
867 name is origin (i.e. wrap around to the longest name, which may still be
868 *origin* due to length considerations.
870 The relativity of the name is preserved, so if this name is relative
871 then the method will return a relative name, and likewise if this name
872 is absolute then the predecessor will be absolute.
874 *prefix_ok* indicates if prefixing labels is allowed, and
875 defaults to ``True``. Normally it is good to allow this, but if computing
876 a maximal predecessor at a zone cut point then ``False`` must be specified.
877 """
878 return _handle_relativity_and_call(
879 _absolute_predecessor, self, origin, prefix_ok
880 )
882 def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
883 """Return the minimal successor of *name* in the DNSSEC ordering in the zone
884 whose origin is *origin*, or return *origin* if the successor cannot be
885 computed due to name length limitations.
887 Note that *origin* is returned in the "too long" cases because wrapping
888 around to the origin is how NSEC records express "end of the zone".
890 The relativity of the name is preserved, so if this name is relative
891 then the method will return a relative name, and likewise if this name
892 is absolute then the successor will be absolute.
894 *prefix_ok* indicates if prefixing a new minimal label is allowed, and
895 defaults to ``True``. Normally it is good to allow this, but if computing
896 a minimal successor at a zone cut point then ``False`` must be specified.
897 """
898 return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok)
901#: The root name, '.'
902root = Name([b""])
904#: The empty name.
905empty = Name([])
908def from_unicode(
909 text: str, origin: Name | None = root, idna_codec: IDNACodec | None = None
910) -> Name:
911 """Convert unicode text into a Name object.
913 Labels are encoded in IDN ACE form according to rules specified by
914 the IDNA codec.
916 *text*, a ``str``, is the text to convert into a name.
918 *origin*, a ``dns.name.Name``, specifies the origin to
919 append to non-absolute names. The default is the root name.
921 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
922 encoder/decoder. If ``None``, the default IDNA encoder/decoder
923 is used.
925 Returns a ``dns.name.Name``.
926 """
928 labels = []
929 label = ""
930 escaping = False
931 edigits = 0
932 total = 0
933 if idna_codec is None:
934 idna_codec = IDNA_DEFAULT
935 if text == "@":
936 text = ""
937 if text:
938 if text in [".", "\u3002", "\uff0e", "\uff61"]:
939 return Name([b""]) # no Unicode "u" on this constant!
940 for c in text:
941 if escaping:
942 if edigits == 0:
943 if c.isdigit():
944 total = int(c)
945 edigits += 1
946 else:
947 label += c
948 escaping = False
949 else:
950 if not c.isdigit():
951 raise BadEscape
952 total *= 10
953 total += int(c)
954 edigits += 1
955 if edigits == 3:
956 escaping = False
957 label += chr(total)
958 elif c in [".", "\u3002", "\uff0e", "\uff61"]:
959 if len(label) == 0:
960 raise EmptyLabel
961 labels.append(idna_codec.encode(label))
962 label = ""
963 elif c == "\\":
964 escaping = True
965 edigits = 0
966 total = 0
967 else:
968 label += c
969 if escaping:
970 raise BadEscape
971 if len(label) > 0:
972 labels.append(idna_codec.encode(label))
973 else:
974 labels.append(b"")
976 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
977 labels.extend(list(origin.labels))
978 return Name(labels)
981def is_all_ascii(text: str) -> bool:
982 for c in text:
983 if ord(c) > 0x7F:
984 return False
985 return True
988def from_text(
989 text: bytes | str,
990 origin: Name | None = root,
991 idna_codec: IDNACodec | None = None,
992) -> Name:
993 """Convert text into a Name object.
995 *text*, a ``bytes`` or ``str``, is the text to convert into a name.
997 *origin*, a ``dns.name.Name``, specifies the origin to
998 append to non-absolute names. The default is the root name.
1000 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
1001 encoder/decoder. If ``None``, the default IDNA encoder/decoder
1002 is used.
1004 Returns a ``dns.name.Name``.
1005 """
1007 if isinstance(text, str):
1008 if not is_all_ascii(text):
1009 # Some codepoint in the input text is > 127, so IDNA applies.
1010 return from_unicode(text, origin, idna_codec)
1011 # The input is all ASCII, so treat this like an ordinary non-IDNA
1012 # domain name. Note that "all ASCII" is about the input text,
1013 # not the codepoints in the domain name. E.g. if text has value
1014 #
1015 # r'\150\151\152\153\154\155\156\157\158\159'
1016 #
1017 # then it's still "all ASCII" even though the domain name has
1018 # codepoints > 127.
1019 text = text.encode("ascii")
1020 labels = []
1021 label = b""
1022 escaping = False
1023 edigits = 0
1024 total = 0
1025 if text == b"@":
1026 text = b""
1027 if text:
1028 if text == b".":
1029 return Name([b""])
1030 for c in text:
1031 byte_ = struct.pack("!B", c)
1032 if escaping:
1033 if edigits == 0:
1034 if byte_.isdigit():
1035 total = int(byte_)
1036 edigits += 1
1037 else:
1038 label += byte_
1039 escaping = False
1040 else:
1041 if not byte_.isdigit():
1042 raise BadEscape
1043 total *= 10
1044 total += int(byte_)
1045 edigits += 1
1046 if edigits == 3:
1047 escaping = False
1048 label += struct.pack("!B", total)
1049 elif byte_ == b".":
1050 if len(label) == 0:
1051 raise EmptyLabel
1052 labels.append(label)
1053 label = b""
1054 elif byte_ == b"\\":
1055 escaping = True
1056 edigits = 0
1057 total = 0
1058 else:
1059 label += byte_
1060 if escaping:
1061 raise BadEscape
1062 if len(label) > 0:
1063 labels.append(label)
1064 else:
1065 labels.append(b"")
1066 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
1067 labels.extend(list(origin.labels))
1068 return Name(labels)
1071def from_wire_parser(parser: dns.wirebase.Parser) -> Name:
1072 """Convert possibly compressed wire format into a Name.
1074 *parser* is a dns.wirebase.Parser.
1076 Raises ``dns.name.BadPointer`` if a compression pointer did not
1077 point backwards in the message.
1079 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
1081 Returns a ``dns.name.Name``
1082 """
1084 labels = []
1085 biggest_pointer = parser.current
1086 with parser.restore_furthest():
1087 count = parser.get_uint8()
1088 while count != 0:
1089 if count < 64:
1090 labels.append(parser.get_bytes(count))
1091 elif count >= 192:
1092 current = (count & 0x3F) * 256 + parser.get_uint8()
1093 if current >= biggest_pointer:
1094 raise BadPointer
1095 biggest_pointer = current
1096 parser.seek(current)
1097 else:
1098 raise BadLabelType
1099 count = parser.get_uint8()
1100 labels.append(b"")
1101 return Name(labels)
1104def from_wire(message: bytes, current: int) -> tuple[Name, int]:
1105 """Convert possibly compressed wire format into a Name.
1107 *message* is a ``bytes`` containing an entire DNS message in DNS
1108 wire form.
1110 *current*, an ``int``, is the offset of the beginning of the name
1111 from the start of the message
1113 Raises ``dns.name.BadPointer`` if a compression pointer did not
1114 point backwards in the message.
1116 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
1118 Returns a ``(dns.name.Name, int)`` tuple consisting of the name
1119 that was read and the number of bytes of the wire format message
1120 which were consumed reading it.
1121 """
1123 parser = dns.wirebase.Parser(message, current)
1124 name = from_wire_parser(parser)
1125 return (name, parser.current - current)
1128# RFC 4471 Support
1130_MINIMAL_OCTET = b"\x00"
1131_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET)
1132_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET])
1133_MAXIMAL_OCTET = b"\xff"
1134_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET)
1135_AT_SIGN_VALUE = ord("@")
1136_LEFT_SQUARE_BRACKET_VALUE = ord("[")
1139def _wire_length(labels):
1140 return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0)
1143def _pad_to_max_name(name):
1144 needed = 255 - _wire_length(name.labels)
1145 new_labels = []
1146 while needed > 64:
1147 new_labels.append(_MAXIMAL_OCTET * 63)
1148 needed -= 64
1149 if needed >= 2:
1150 new_labels.append(_MAXIMAL_OCTET * (needed - 1))
1151 # Note we're already maximal in the needed == 1 case as while we'd like
1152 # to add one more byte as a new label, we can't, as adding a new non-empty
1153 # label requires at least 2 bytes.
1154 new_labels = list(reversed(new_labels))
1155 new_labels.extend(name.labels)
1156 return Name(new_labels)
1159def _pad_to_max_label(label, suffix_labels):
1160 length = len(label)
1161 # We have to subtract one here to account for the length byte of label.
1162 remaining = 255 - _wire_length(suffix_labels) - length - 1
1163 if remaining <= 0:
1164 # Shouldn't happen!
1165 return label
1166 needed = min(63 - length, remaining)
1167 return label + _MAXIMAL_OCTET * needed
1170def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1171 # This is the RFC 4471 predecessor algorithm using the "absolute method" of section
1172 # 3.1.1.
1173 #
1174 # Our caller must ensure that the name and origin are absolute, and that name is a
1175 # subdomain of origin.
1176 if name == origin:
1177 return _pad_to_max_name(name)
1178 least_significant_label = name[0]
1179 if least_significant_label == _MINIMAL_OCTET:
1180 return name.parent()
1181 least_octet = least_significant_label[-1]
1182 suffix_labels = name.labels[1:]
1183 if least_octet == _MINIMAL_OCTET_VALUE:
1184 new_labels = [least_significant_label[:-1]]
1185 else:
1186 octets = bytearray(least_significant_label)
1187 octet = octets[-1]
1188 if octet == _LEFT_SQUARE_BRACKET_VALUE:
1189 octet = _AT_SIGN_VALUE
1190 else:
1191 octet -= 1
1192 octets[-1] = octet
1193 least_significant_label = bytes(octets)
1194 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)]
1195 new_labels.extend(suffix_labels)
1196 name = Name(new_labels)
1197 if prefix_ok:
1198 return _pad_to_max_name(name)
1199 else:
1200 return name
1203def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1204 # This is the RFC 4471 successor algorithm using the "absolute method" of section
1205 # 3.1.2.
1206 #
1207 # Our caller must ensure that the name and origin are absolute, and that name is a
1208 # subdomain of origin.
1209 if prefix_ok:
1210 # Try prefixing \000 as new label
1211 try:
1212 return _SUCCESSOR_PREFIX.concatenate(name)
1213 except NameTooLong:
1214 pass
1215 while name != origin:
1216 # Try extending the least significant label.
1217 least_significant_label = name[0]
1218 if len(least_significant_label) < 63:
1219 # We may be able to extend the least label with a minimal additional byte.
1220 # This is only "may" because we could have a maximal length name even though
1221 # the least significant label isn't maximally long.
1222 new_labels = [least_significant_label + _MINIMAL_OCTET]
1223 new_labels.extend(name.labels[1:])
1224 try:
1225 return Name(new_labels)
1226 except NameTooLong:
1227 pass
1228 # We can't extend the label either, so we'll try to increment the least
1229 # signficant non-maximal byte in it.
1230 octets = bytearray(least_significant_label)
1231 # We do this reversed iteration with an explicit indexing variable because
1232 # if we find something to increment, we're going to want to truncate everything
1233 # to the right of it.
1234 for i in range(len(octets) - 1, -1, -1):
1235 octet = octets[i]
1236 if octet == _MAXIMAL_OCTET_VALUE:
1237 # We can't increment this, so keep looking.
1238 continue
1239 # Finally, something we can increment. We have to apply a special rule for
1240 # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when
1241 # comparing names, uppercase letters compare as if they were their
1242 # lower-case equivalents. If we increment "@" to "A", then it would compare
1243 # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have
1244 # skipped the most minimal successor, namely "[".
1245 if octet == _AT_SIGN_VALUE:
1246 octet = _LEFT_SQUARE_BRACKET_VALUE
1247 else:
1248 octet += 1
1249 octets[i] = octet
1250 # We can now truncate all of the maximal values we skipped (if any)
1251 new_labels = [bytes(octets[: i + 1])]
1252 new_labels.extend(name.labels[1:])
1253 # We haven't changed the length of the name, so the Name constructor will
1254 # always work.
1255 return Name(new_labels)
1256 # We couldn't increment, so chop off the least significant label and try
1257 # again.
1258 name = name.parent()
1260 # We couldn't increment at all, so return the origin, as wrapping around is the
1261 # DNSSEC way.
1262 return origin
1265def _handle_relativity_and_call(
1266 function: Callable[[Name, Name, bool], Name],
1267 name: Name,
1268 origin: Name,
1269 prefix_ok: bool,
1270) -> Name:
1271 # Make "name" absolute if needed, ensure that the origin is absolute,
1272 # call function(), and then relativize the result if needed.
1273 if not origin.is_absolute():
1274 raise NeedAbsoluteNameOrOrigin
1275 relative = not name.is_absolute()
1276 if relative:
1277 name = name.derelativize(origin)
1278 elif not name.is_subdomain(origin):
1279 raise NeedSubdomainOfOrigin
1280 result_name = function(name, origin, prefix_ok)
1281 if relative:
1282 result_name = result_name.relativize(origin)
1283 return result_name