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 typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union
26import dns._features
27import dns.enum
28import dns.exception
29import dns.immutable
30import dns.wire
32# Dnspython will never access idna if the import fails, but pyright can't figure
33# that out, so...
34#
35# pyright: reportAttributeAccessIssue = false, reportPossiblyUnboundVariable = false
37if dns._features.have("idna"):
38 import idna # type: ignore
40 have_idna_2008 = True
41else: # pragma: no cover
42 have_idna_2008 = False
45CompressType = Dict["Name", int]
48class NameRelation(dns.enum.IntEnum):
49 """Name relation result from fullcompare()."""
51 # This is an IntEnum for backwards compatibility in case anyone
52 # has hardwired the constants.
54 #: The compared names have no relationship to each other.
55 NONE = 0
56 #: the first name is a superdomain of the second.
57 SUPERDOMAIN = 1
58 #: The first name is a subdomain of the second.
59 SUBDOMAIN = 2
60 #: The compared names are equal.
61 EQUAL = 3
62 #: The compared names have a common ancestor.
63 COMMONANCESTOR = 4
65 @classmethod
66 def _maximum(cls):
67 return cls.COMMONANCESTOR # pragma: no cover
69 @classmethod
70 def _short_name(cls):
71 return cls.__name__ # pragma: no cover
74# Backwards compatibility
75NAMERELN_NONE = NameRelation.NONE
76NAMERELN_SUPERDOMAIN = NameRelation.SUPERDOMAIN
77NAMERELN_SUBDOMAIN = NameRelation.SUBDOMAIN
78NAMERELN_EQUAL = NameRelation.EQUAL
79NAMERELN_COMMONANCESTOR = NameRelation.COMMONANCESTOR
82class EmptyLabel(dns.exception.SyntaxError):
83 """A DNS label is empty."""
86class BadEscape(dns.exception.SyntaxError):
87 """An escaped code in a text format of DNS name is invalid."""
90class BadPointer(dns.exception.FormError):
91 """A DNS compression pointer points forward instead of backward."""
94class BadLabelType(dns.exception.FormError):
95 """The label type in DNS name wire format is unknown."""
98class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):
99 """An attempt was made to convert a non-absolute name to
100 wire when there was also a non-absolute (or missing) origin."""
103class NameTooLong(dns.exception.FormError):
104 """A DNS name is > 255 octets long."""
107class LabelTooLong(dns.exception.SyntaxError):
108 """A DNS label is > 63 octets long."""
111class AbsoluteConcatenation(dns.exception.DNSException):
112 """An attempt was made to append anything other than the
113 empty name to an absolute DNS name."""
116class NoParent(dns.exception.DNSException):
117 """An attempt was made to get the parent of the root name
118 or the empty name."""
121class NoIDNA2008(dns.exception.DNSException):
122 """IDNA 2008 processing was requested but the idna module is not
123 available."""
126class IDNAException(dns.exception.DNSException):
127 """IDNA processing raised an exception."""
129 supp_kwargs = {"idna_exception"}
130 fmt = "IDNA processing exception: {idna_exception}"
132 # We do this as otherwise mypy complains about unexpected keyword argument
133 # idna_exception
134 def __init__(self, *args, **kwargs):
135 super().__init__(*args, **kwargs)
138class NeedSubdomainOfOrigin(dns.exception.DNSException):
139 """An absolute name was provided that is not a subdomain of the specified origin."""
142_escaped = b'"().;\\@$'
143_escaped_text = '"().;\\@$'
146def _escapify(label: Union[bytes, str]) -> str:
147 """Escape the characters in label which need it.
148 @returns: the escaped string
149 @rtype: string"""
150 if isinstance(label, bytes):
151 # Ordinary DNS label mode. Escape special characters and values
152 # < 0x20 or > 0x7f.
153 text = ""
154 for c in label:
155 if c in _escaped:
156 text += "\\" + chr(c)
157 elif c > 0x20 and c < 0x7F:
158 text += chr(c)
159 else:
160 text += f"\\{c:03d}"
161 return text
163 # Unicode label mode. Escape only special characters and values < 0x20
164 text = ""
165 for uc in label:
166 if uc in _escaped_text:
167 text += "\\" + uc
168 elif uc <= "\x20":
169 text += f"\\{ord(uc):03d}"
170 else:
171 text += uc
172 return text
175class IDNACodec:
176 """Abstract base class for IDNA encoder/decoders."""
178 def __init__(self):
179 pass
181 def is_idna(self, label: bytes) -> bool:
182 return label.lower().startswith(b"xn--")
184 def encode(self, label: str) -> bytes:
185 raise NotImplementedError # pragma: no cover
187 def decode(self, label: bytes) -> str:
188 # We do not apply any IDNA policy on decode.
189 if self.is_idna(label):
190 try:
191 slabel = label[4:].decode("punycode")
192 return _escapify(slabel)
193 except Exception as e:
194 raise IDNAException(idna_exception=e)
195 else:
196 return _escapify(label)
199class IDNA2003Codec(IDNACodec):
200 """IDNA 2003 encoder/decoder."""
202 def __init__(self, strict_decode: bool = False):
203 """Initialize the IDNA 2003 encoder/decoder.
205 *strict_decode* is a ``bool``. If `True`, then IDNA2003 checking
206 is done when decoding. This can cause failures if the name
207 was encoded with IDNA2008. The default is `False`.
208 """
210 super().__init__()
211 self.strict_decode = strict_decode
213 def encode(self, label: str) -> bytes:
214 """Encode *label*."""
216 if label == "":
217 return b""
218 try:
219 return encodings.idna.ToASCII(label)
220 except UnicodeError:
221 raise LabelTooLong
223 def decode(self, label: bytes) -> str:
224 """Decode *label*."""
225 if not self.strict_decode:
226 return super().decode(label)
227 if label == b"":
228 return ""
229 try:
230 return _escapify(encodings.idna.ToUnicode(label))
231 except Exception as e:
232 raise IDNAException(idna_exception=e)
235class IDNA2008Codec(IDNACodec):
236 """IDNA 2008 encoder/decoder."""
238 def __init__(
239 self,
240 uts_46: bool = False,
241 transitional: bool = False,
242 allow_pure_ascii: bool = False,
243 strict_decode: bool = False,
244 ):
245 """Initialize the IDNA 2008 encoder/decoder.
247 *uts_46* is a ``bool``. If True, apply Unicode IDNA
248 compatibility processing as described in Unicode Technical
249 Standard #46 (https://unicode.org/reports/tr46/).
250 If False, do not apply the mapping. The default is False.
252 *transitional* is a ``bool``: If True, use the
253 "transitional" mode described in Unicode Technical Standard
254 #46. The default is False.
256 *allow_pure_ascii* is a ``bool``. If True, then a label which
257 consists of only ASCII characters is allowed. This is less
258 strict than regular IDNA 2008, but is also necessary for mixed
259 names, e.g. a name with starting with "_sip._tcp." and ending
260 in an IDN suffix which would otherwise be disallowed. The
261 default is False.
263 *strict_decode* is a ``bool``: If True, then IDNA2008 checking
264 is done when decoding. This can cause failures if the name
265 was encoded with IDNA2003. The default is False.
266 """
267 super().__init__()
268 self.uts_46 = uts_46
269 self.transitional = transitional
270 self.allow_pure_ascii = allow_pure_ascii
271 self.strict_decode = strict_decode
273 def encode(self, label: str) -> bytes:
274 if label == "":
275 return b""
276 if self.allow_pure_ascii and is_all_ascii(label):
277 encoded = label.encode("ascii")
278 if len(encoded) > 63:
279 raise LabelTooLong
280 return encoded
281 if not have_idna_2008:
282 raise NoIDNA2008
283 try:
284 if self.uts_46:
285 # pylint: disable=possibly-used-before-assignment
286 label = idna.uts46_remap(label, False, self.transitional)
287 return idna.alabel(label)
288 except idna.IDNAError as e:
289 if e.args[0] == "Label too long":
290 raise LabelTooLong
291 else:
292 raise IDNAException(idna_exception=e)
294 def decode(self, label: bytes) -> str:
295 if not self.strict_decode:
296 return super().decode(label)
297 if label == b"":
298 return ""
299 if not have_idna_2008:
300 raise NoIDNA2008
301 try:
302 ulabel = idna.ulabel(label)
303 if self.uts_46:
304 ulabel = idna.uts46_remap(ulabel, False, self.transitional)
305 return _escapify(ulabel)
306 except (idna.IDNAError, UnicodeError) as e:
307 raise IDNAException(idna_exception=e)
310IDNA_2003_Practical = IDNA2003Codec(False)
311IDNA_2003_Strict = IDNA2003Codec(True)
312IDNA_2003 = IDNA_2003_Practical
313IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)
314IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)
315IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)
316IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)
317IDNA_2008 = IDNA_2008_Practical
320def _validate_labels(labels: Tuple[bytes, ...]) -> None:
321 """Check for empty labels in the middle of a label sequence,
322 labels that are too long, and for too many labels.
324 Raises ``dns.name.NameTooLong`` if the name as a whole is too long.
326 Raises ``dns.name.EmptyLabel`` if a label is empty (i.e. the root
327 label) and appears in a position other than the end of the label
328 sequence
330 """
332 l = len(labels)
333 total = 0
334 i = -1
335 j = 0
336 for label in labels:
337 ll = len(label)
338 total += ll + 1
339 if ll > 63:
340 raise LabelTooLong
341 if i < 0 and label == b"":
342 i = j
343 j += 1
344 if total > 255:
345 raise NameTooLong
346 if i >= 0 and i != l - 1:
347 raise EmptyLabel
350def _maybe_convert_to_binary(label: Union[bytes, str]) -> bytes:
351 """If label is ``str``, convert it to ``bytes``. If it is already
352 ``bytes`` just return it.
354 """
356 if isinstance(label, bytes):
357 return label
358 if isinstance(label, str):
359 return label.encode()
360 raise ValueError # pragma: no cover
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[Union[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: Optional[IDNACodec] = 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: Optional["Name"] = 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: Optional[Any] = None,
653 compress: Optional[CompressType] = None,
654 origin: Optional["Name"] = None,
655 canonicalize: bool = False,
656 ) -> Optional[bytes]:
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):
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, dns.name.empty)
768 elif depth == l:
769 return (dns.name.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 origin is not None and 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: Optional["Name"] = 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: Optional[Name] = root, idna_codec: Optional[IDNACodec] = 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 if not isinstance(text, str):
922 raise ValueError("input to from_unicode() must be a unicode string")
923 if not (origin is None or isinstance(origin, Name)):
924 raise ValueError("origin must be a Name or None")
925 labels = []
926 label = ""
927 escaping = False
928 edigits = 0
929 total = 0
930 if idna_codec is None:
931 idna_codec = IDNA_2003
932 if text == "@":
933 text = ""
934 if text:
935 if text in [".", "\u3002", "\uff0e", "\uff61"]:
936 return Name([b""]) # no Unicode "u" on this constant!
937 for c in text:
938 if escaping:
939 if edigits == 0:
940 if c.isdigit():
941 total = int(c)
942 edigits += 1
943 else:
944 label += c
945 escaping = False
946 else:
947 if not c.isdigit():
948 raise BadEscape
949 total *= 10
950 total += int(c)
951 edigits += 1
952 if edigits == 3:
953 escaping = False
954 label += chr(total)
955 elif c in [".", "\u3002", "\uff0e", "\uff61"]:
956 if len(label) == 0:
957 raise EmptyLabel
958 labels.append(idna_codec.encode(label))
959 label = ""
960 elif c == "\\":
961 escaping = True
962 edigits = 0
963 total = 0
964 else:
965 label += c
966 if escaping:
967 raise BadEscape
968 if len(label) > 0:
969 labels.append(idna_codec.encode(label))
970 else:
971 labels.append(b"")
973 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
974 labels.extend(list(origin.labels))
975 return Name(labels)
978def is_all_ascii(text: str) -> bool:
979 for c in text:
980 if ord(c) > 0x7F:
981 return False
982 return True
985def from_text(
986 text: Union[bytes, str],
987 origin: Optional[Name] = root,
988 idna_codec: Optional[IDNACodec] = None,
989) -> Name:
990 """Convert text into a Name object.
992 *text*, a ``bytes`` or ``str``, is the text to convert into a name.
994 *origin*, a ``dns.name.Name``, specifies the origin to
995 append to non-absolute names. The default is the root name.
997 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
998 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
999 is used.
1001 Returns a ``dns.name.Name``.
1002 """
1004 if isinstance(text, str):
1005 if not is_all_ascii(text):
1006 # Some codepoint in the input text is > 127, so IDNA applies.
1007 return from_unicode(text, origin, idna_codec)
1008 # The input is all ASCII, so treat this like an ordinary non-IDNA
1009 # domain name. Note that "all ASCII" is about the input text,
1010 # not the codepoints in the domain name. E.g. if text has value
1011 #
1012 # r'\150\151\152\153\154\155\156\157\158\159'
1013 #
1014 # then it's still "all ASCII" even though the domain name has
1015 # codepoints > 127.
1016 text = text.encode("ascii")
1017 if not isinstance(text, bytes):
1018 raise ValueError("input to from_text() must be a string")
1019 if not (origin is None or isinstance(origin, Name)):
1020 raise ValueError("origin must be a Name or None")
1021 labels = []
1022 label = b""
1023 escaping = False
1024 edigits = 0
1025 total = 0
1026 if text == b"@":
1027 text = b""
1028 if text:
1029 if text == b".":
1030 return Name([b""])
1031 for c in text:
1032 byte_ = struct.pack("!B", c)
1033 if escaping:
1034 if edigits == 0:
1035 if byte_.isdigit():
1036 total = int(byte_)
1037 edigits += 1
1038 else:
1039 label += byte_
1040 escaping = False
1041 else:
1042 if not byte_.isdigit():
1043 raise BadEscape
1044 total *= 10
1045 total += int(byte_)
1046 edigits += 1
1047 if edigits == 3:
1048 escaping = False
1049 label += struct.pack("!B", total)
1050 elif byte_ == b".":
1051 if len(label) == 0:
1052 raise EmptyLabel
1053 labels.append(label)
1054 label = b""
1055 elif byte_ == b"\\":
1056 escaping = True
1057 edigits = 0
1058 total = 0
1059 else:
1060 label += byte_
1061 if escaping:
1062 raise BadEscape
1063 if len(label) > 0:
1064 labels.append(label)
1065 else:
1066 labels.append(b"")
1067 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
1068 labels.extend(list(origin.labels))
1069 return Name(labels)
1072# we need 'dns.wire.Parser' quoted as dns.name and dns.wire depend on each other.
1075def from_wire_parser(parser: "dns.wire.Parser") -> Name:
1076 """Convert possibly compressed wire format into a Name.
1078 *parser* is a dns.wire.Parser.
1080 Raises ``dns.name.BadPointer`` if a compression pointer did not
1081 point backwards in the message.
1083 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
1085 Returns a ``dns.name.Name``
1086 """
1088 labels = []
1089 biggest_pointer = parser.current
1090 with parser.restore_furthest():
1091 count = parser.get_uint8()
1092 while count != 0:
1093 if count < 64:
1094 labels.append(parser.get_bytes(count))
1095 elif count >= 192:
1096 current = (count & 0x3F) * 256 + parser.get_uint8()
1097 if current >= biggest_pointer:
1098 raise BadPointer
1099 biggest_pointer = current
1100 parser.seek(current)
1101 else:
1102 raise BadLabelType
1103 count = parser.get_uint8()
1104 labels.append(b"")
1105 return Name(labels)
1108def from_wire(message: bytes, current: int) -> Tuple[Name, int]:
1109 """Convert possibly compressed wire format into a Name.
1111 *message* is a ``bytes`` containing an entire DNS message in DNS
1112 wire form.
1114 *current*, an ``int``, is the offset of the beginning of the name
1115 from the start of the message
1117 Raises ``dns.name.BadPointer`` if a compression pointer did not
1118 point backwards in the message.
1120 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
1122 Returns a ``(dns.name.Name, int)`` tuple consisting of the name
1123 that was read and the number of bytes of the wire format message
1124 which were consumed reading it.
1125 """
1127 if not isinstance(message, bytes):
1128 raise ValueError("input to from_wire() must be a byte string")
1129 parser = dns.wire.Parser(message, current)
1130 name = from_wire_parser(parser)
1131 return (name, parser.current - current)
1134# RFC 4471 Support
1136_MINIMAL_OCTET = b"\x00"
1137_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET)
1138_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET])
1139_MAXIMAL_OCTET = b"\xff"
1140_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET)
1141_AT_SIGN_VALUE = ord("@")
1142_LEFT_SQUARE_BRACKET_VALUE = ord("[")
1145def _wire_length(labels):
1146 return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0)
1149def _pad_to_max_name(name):
1150 needed = 255 - _wire_length(name.labels)
1151 new_labels = []
1152 while needed > 64:
1153 new_labels.append(_MAXIMAL_OCTET * 63)
1154 needed -= 64
1155 if needed >= 2:
1156 new_labels.append(_MAXIMAL_OCTET * (needed - 1))
1157 # Note we're already maximal in the needed == 1 case as while we'd like
1158 # to add one more byte as a new label, we can't, as adding a new non-empty
1159 # label requires at least 2 bytes.
1160 new_labels = list(reversed(new_labels))
1161 new_labels.extend(name.labels)
1162 return Name(new_labels)
1165def _pad_to_max_label(label, suffix_labels):
1166 length = len(label)
1167 # We have to subtract one here to account for the length byte of label.
1168 remaining = 255 - _wire_length(suffix_labels) - length - 1
1169 if remaining <= 0:
1170 # Shouldn't happen!
1171 return label
1172 needed = min(63 - length, remaining)
1173 return label + _MAXIMAL_OCTET * needed
1176def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1177 # This is the RFC 4471 predecessor algorithm using the "absolute method" of section
1178 # 3.1.1.
1179 #
1180 # Our caller must ensure that the name and origin are absolute, and that name is a
1181 # subdomain of origin.
1182 if name == origin:
1183 return _pad_to_max_name(name)
1184 least_significant_label = name[0]
1185 if least_significant_label == _MINIMAL_OCTET:
1186 return name.parent()
1187 least_octet = least_significant_label[-1]
1188 suffix_labels = name.labels[1:]
1189 if least_octet == _MINIMAL_OCTET_VALUE:
1190 new_labels = [least_significant_label[:-1]]
1191 else:
1192 octets = bytearray(least_significant_label)
1193 octet = octets[-1]
1194 if octet == _LEFT_SQUARE_BRACKET_VALUE:
1195 octet = _AT_SIGN_VALUE
1196 else:
1197 octet -= 1
1198 octets[-1] = octet
1199 least_significant_label = bytes(octets)
1200 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)]
1201 new_labels.extend(suffix_labels)
1202 name = Name(new_labels)
1203 if prefix_ok:
1204 return _pad_to_max_name(name)
1205 else:
1206 return name
1209def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1210 # This is the RFC 4471 successor algorithm using the "absolute method" of section
1211 # 3.1.2.
1212 #
1213 # Our caller must ensure that the name and origin are absolute, and that name is a
1214 # subdomain of origin.
1215 if prefix_ok:
1216 # Try prefixing \000 as new label
1217 try:
1218 return _SUCCESSOR_PREFIX.concatenate(name)
1219 except NameTooLong:
1220 pass
1221 while name != origin:
1222 # Try extending the least significant label.
1223 least_significant_label = name[0]
1224 if len(least_significant_label) < 63:
1225 # We may be able to extend the least label with a minimal additional byte.
1226 # This is only "may" because we could have a maximal length name even though
1227 # the least significant label isn't maximally long.
1228 new_labels = [least_significant_label + _MINIMAL_OCTET]
1229 new_labels.extend(name.labels[1:])
1230 try:
1231 return dns.name.Name(new_labels)
1232 except dns.name.NameTooLong:
1233 pass
1234 # We can't extend the label either, so we'll try to increment the least
1235 # signficant non-maximal byte in it.
1236 octets = bytearray(least_significant_label)
1237 # We do this reversed iteration with an explicit indexing variable because
1238 # if we find something to increment, we're going to want to truncate everything
1239 # to the right of it.
1240 for i in range(len(octets) - 1, -1, -1):
1241 octet = octets[i]
1242 if octet == _MAXIMAL_OCTET_VALUE:
1243 # We can't increment this, so keep looking.
1244 continue
1245 # Finally, something we can increment. We have to apply a special rule for
1246 # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when
1247 # comparing names, uppercase letters compare as if they were their
1248 # lower-case equivalents. If we increment "@" to "A", then it would compare
1249 # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have
1250 # skipped the most minimal successor, namely "[".
1251 if octet == _AT_SIGN_VALUE:
1252 octet = _LEFT_SQUARE_BRACKET_VALUE
1253 else:
1254 octet += 1
1255 octets[i] = octet
1256 # We can now truncate all of the maximal values we skipped (if any)
1257 new_labels = [bytes(octets[: i + 1])]
1258 new_labels.extend(name.labels[1:])
1259 # We haven't changed the length of the name, so the Name constructor will
1260 # always work.
1261 return Name(new_labels)
1262 # We couldn't increment, so chop off the least significant label and try
1263 # again.
1264 name = name.parent()
1266 # We couldn't increment at all, so return the origin, as wrapping around is the
1267 # DNSSEC way.
1268 return origin
1271def _handle_relativity_and_call(
1272 function: Callable[[Name, Name, bool], Name],
1273 name: Name,
1274 origin: Name,
1275 prefix_ok: bool,
1276) -> Name:
1277 # Make "name" absolute if needed, ensure that the origin is absolute,
1278 # call function(), and then relativize the result if needed.
1279 if not origin.is_absolute():
1280 raise NeedAbsoluteNameOrOrigin
1281 relative = not name.is_absolute()
1282 if relative:
1283 name = name.derelativize(origin)
1284 elif not name.is_subdomain(origin):
1285 raise NeedSubdomainOfOrigin
1286 result_name = function(name, origin, prefix_ok)
1287 if relative:
1288 result_name = result_name.relativize(origin)
1289 return result_name