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.
19"""
21import copy
22import encodings.idna # type: ignore
23import functools
24import struct
25from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union
27import dns._features
28import dns.enum
29import dns.exception
30import dns.immutable
31import dns.wire
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: Union[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: Union[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 if isinstance(label, str):
360 return label.encode()
361 raise ValueError # pragma: no cover
364@dns.immutable.immutable
365class Name:
366 """A DNS name.
368 The dns.name.Name class represents a DNS name as a tuple of
369 labels. Each label is a ``bytes`` in DNS wire format. Instances
370 of the class are immutable.
371 """
373 __slots__ = ["labels"]
375 def __init__(self, labels: Iterable[Union[bytes, str]]):
376 """*labels* is any iterable whose values are ``str`` or ``bytes``."""
378 blabels = [_maybe_convert_to_binary(x) for x in labels]
379 self.labels = tuple(blabels)
380 _validate_labels(self.labels)
382 def __copy__(self):
383 return Name(self.labels)
385 def __deepcopy__(self, memo):
386 return Name(copy.deepcopy(self.labels, memo))
388 def __getstate__(self):
389 # Names can be pickled
390 return {"labels": self.labels}
392 def __setstate__(self, state):
393 super().__setattr__("labels", state["labels"])
394 _validate_labels(self.labels)
396 def is_absolute(self) -> bool:
397 """Is the most significant label of this name the root label?
399 Returns a ``bool``.
400 """
402 return len(self.labels) > 0 and self.labels[-1] == b""
404 def is_wild(self) -> bool:
405 """Is this name wild? (I.e. Is the least significant label '*'?)
407 Returns a ``bool``.
408 """
410 return len(self.labels) > 0 and self.labels[0] == b"*"
412 def __hash__(self) -> int:
413 """Return a case-insensitive hash of the name.
415 Returns an ``int``.
416 """
418 h = 0
419 for label in self.labels:
420 for c in label.lower():
421 h += (h << 3) + c
422 return h
424 def fullcompare(self, other: "Name") -> Tuple[NameRelation, int, int]:
425 """Compare two names, returning a 3-tuple
426 ``(relation, order, nlabels)``.
428 *relation* describes the relation ship between the names,
429 and is one of: ``dns.name.NameRelation.NONE``,
430 ``dns.name.NameRelation.SUPERDOMAIN``, ``dns.name.NameRelation.SUBDOMAIN``,
431 ``dns.name.NameRelation.EQUAL``, or ``dns.name.NameRelation.COMMONANCESTOR``.
433 *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==
434 0 if *self* == *other*. A relative name is always less than an
435 absolute name. If both names have the same relativity, then
436 the DNSSEC order relation is used to order them.
438 *nlabels* is the number of significant labels that the two names
439 have in common.
441 Here are some examples. Names ending in "." are absolute names,
442 those not ending in "." are relative names.
444 ============= ============= =========== ===== =======
445 self other relation order nlabels
446 ============= ============= =========== ===== =======
447 www.example. www.example. equal 0 3
448 www.example. example. subdomain > 0 2
449 example. www.example. superdomain < 0 2
450 example1.com. example2.com. common anc. < 0 2
451 example1 example2. none < 0 0
452 example1. example2 none > 0 0
453 ============= ============= =========== ===== =======
454 """
456 sabs = self.is_absolute()
457 oabs = other.is_absolute()
458 if sabs != oabs:
459 if sabs:
460 return (NameRelation.NONE, 1, 0)
461 else:
462 return (NameRelation.NONE, -1, 0)
463 l1 = len(self.labels)
464 l2 = len(other.labels)
465 ldiff = l1 - l2
466 if ldiff < 0:
467 l = l1
468 else:
469 l = l2
471 order = 0
472 nlabels = 0
473 namereln = NameRelation.NONE
474 while l > 0:
475 l -= 1
476 l1 -= 1
477 l2 -= 1
478 label1 = self.labels[l1].lower()
479 label2 = other.labels[l2].lower()
480 if label1 < label2:
481 order = -1
482 if nlabels > 0:
483 namereln = NameRelation.COMMONANCESTOR
484 return (namereln, order, nlabels)
485 elif label1 > label2:
486 order = 1
487 if nlabels > 0:
488 namereln = NameRelation.COMMONANCESTOR
489 return (namereln, order, nlabels)
490 nlabels += 1
491 order = ldiff
492 if ldiff < 0:
493 namereln = NameRelation.SUPERDOMAIN
494 elif ldiff > 0:
495 namereln = NameRelation.SUBDOMAIN
496 else:
497 namereln = NameRelation.EQUAL
498 return (namereln, order, nlabels)
500 def is_subdomain(self, other: "Name") -> bool:
501 """Is self a subdomain of other?
503 Note that the notion of subdomain includes equality, e.g.
504 "dnspython.org" is a subdomain of itself.
506 Returns a ``bool``.
507 """
509 (nr, _, _) = self.fullcompare(other)
510 if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL:
511 return True
512 return False
514 def is_superdomain(self, other: "Name") -> bool:
515 """Is self a superdomain of other?
517 Note that the notion of superdomain includes equality, e.g.
518 "dnspython.org" is a superdomain of itself.
520 Returns a ``bool``.
521 """
523 (nr, _, _) = self.fullcompare(other)
524 if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL:
525 return True
526 return False
528 def canonicalize(self) -> "Name":
529 """Return a name which is equal to the current name, but is in
530 DNSSEC canonical form.
531 """
533 return Name([x.lower() for x in self.labels])
535 def __eq__(self, other):
536 if isinstance(other, Name):
537 return self.fullcompare(other)[1] == 0
538 else:
539 return False
541 def __ne__(self, other):
542 if isinstance(other, Name):
543 return self.fullcompare(other)[1] != 0
544 else:
545 return True
547 def __lt__(self, other):
548 if isinstance(other, Name):
549 return self.fullcompare(other)[1] < 0
550 else:
551 return NotImplemented
553 def __le__(self, other):
554 if isinstance(other, Name):
555 return self.fullcompare(other)[1] <= 0
556 else:
557 return NotImplemented
559 def __ge__(self, other):
560 if isinstance(other, Name):
561 return self.fullcompare(other)[1] >= 0
562 else:
563 return NotImplemented
565 def __gt__(self, other):
566 if isinstance(other, Name):
567 return self.fullcompare(other)[1] > 0
568 else:
569 return NotImplemented
571 def __repr__(self):
572 return "<DNS name " + self.__str__() + ">"
574 def __str__(self):
575 return self.to_text(False)
577 def to_text(self, omit_final_dot: bool = False) -> str:
578 """Convert name to DNS text format.
580 *omit_final_dot* is a ``bool``. If True, don't emit the final
581 dot (denoting the root label) for absolute names. The default
582 is False.
584 Returns a ``str``.
585 """
587 if len(self.labels) == 0:
588 return "@"
589 if len(self.labels) == 1 and self.labels[0] == b"":
590 return "."
591 if omit_final_dot and self.is_absolute():
592 l = self.labels[:-1]
593 else:
594 l = self.labels
595 s = ".".join(map(_escapify, l))
596 return s
598 def to_unicode(
599 self, omit_final_dot: bool = False, idna_codec: Optional[IDNACodec] = None
600 ) -> str:
601 """Convert name to Unicode text format.
603 IDN ACE labels are converted to Unicode.
605 *omit_final_dot* is a ``bool``. If True, don't emit the final
606 dot (denoting the root label) for absolute names. The default
607 is False.
608 *idna_codec* specifies the IDNA encoder/decoder. If None, the
609 dns.name.IDNA_2003_Practical encoder/decoder is used.
610 The IDNA_2003_Practical decoder does
611 not impose any policy, it just decodes punycode, so if you
612 don't want checking for compliance, you can use this decoder
613 for IDNA2008 as well.
615 Returns a ``str``.
616 """
618 if len(self.labels) == 0:
619 return "@"
620 if len(self.labels) == 1 and self.labels[0] == b"":
621 return "."
622 if omit_final_dot and self.is_absolute():
623 l = self.labels[:-1]
624 else:
625 l = self.labels
626 if idna_codec is None:
627 idna_codec = IDNA_2003_Practical
628 return ".".join([idna_codec.decode(x) for x in l])
630 def to_digestable(self, origin: Optional["Name"] = None) -> bytes:
631 """Convert name to a format suitable for digesting in hashes.
633 The name is canonicalized and converted to uncompressed wire
634 format. All names in wire format are absolute. If the name
635 is a relative name, then an origin must be supplied.
637 *origin* is a ``dns.name.Name`` or ``None``. If the name is
638 relative and origin is not ``None``, then origin will be appended
639 to the name.
641 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
642 relative and no origin was provided.
644 Returns a ``bytes``.
645 """
647 digest = self.to_wire(origin=origin, canonicalize=True)
648 assert digest is not None
649 return digest
651 def to_wire(
652 self,
653 file: Optional[Any] = None,
654 compress: Optional[CompressType] = None,
655 origin: Optional["Name"] = None,
656 canonicalize: bool = False,
657 ) -> Optional[bytes]:
658 """Convert name to wire format, possibly compressing it.
660 *file* is the file where the name is emitted (typically an
661 io.BytesIO file). If ``None`` (the default), a ``bytes``
662 containing the wire name will be returned.
664 *compress*, a ``dict``, is the compression table to use. If
665 ``None`` (the default), names will not be compressed. Note that
666 the compression code assumes that compression offset 0 is the
667 start of *file*, and thus compression will not be correct
668 if this is not the case.
670 *origin* is a ``dns.name.Name`` or ``None``. If the name is
671 relative and origin is not ``None``, then *origin* will be appended
672 to it.
674 *canonicalize*, a ``bool``, indicates whether the name should
675 be canonicalized; that is, converted to a format suitable for
676 digesting in hashes.
678 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
679 relative and no origin was provided.
681 Returns a ``bytes`` or ``None``.
682 """
684 if file is None:
685 out = bytearray()
686 for label in self.labels:
687 out.append(len(label))
688 if canonicalize:
689 out += label.lower()
690 else:
691 out += label
692 if not self.is_absolute():
693 if origin is None or not origin.is_absolute():
694 raise NeedAbsoluteNameOrOrigin
695 for label in origin.labels:
696 out.append(len(label))
697 if canonicalize:
698 out += label.lower()
699 else:
700 out += label
701 return bytes(out)
703 labels: Iterable[bytes]
704 if not self.is_absolute():
705 if origin is None or not origin.is_absolute():
706 raise NeedAbsoluteNameOrOrigin
707 labels = list(self.labels)
708 labels.extend(list(origin.labels))
709 else:
710 labels = self.labels
711 i = 0
712 for label in labels:
713 n = Name(labels[i:])
714 i += 1
715 if compress is not None:
716 pos = compress.get(n)
717 else:
718 pos = None
719 if pos is not None:
720 value = 0xC000 + pos
721 s = struct.pack("!H", value)
722 file.write(s)
723 break
724 else:
725 if compress is not None and len(n) > 1:
726 pos = file.tell()
727 if pos <= 0x3FFF:
728 compress[n] = pos
729 l = len(label)
730 file.write(struct.pack("!B", l))
731 if l > 0:
732 if canonicalize:
733 file.write(label.lower())
734 else:
735 file.write(label)
736 return None
738 def __len__(self) -> int:
739 """The length of the name (in labels).
741 Returns an ``int``.
742 """
744 return len(self.labels)
746 def __getitem__(self, index):
747 return self.labels[index]
749 def __add__(self, other):
750 return self.concatenate(other)
752 def __sub__(self, other):
753 return self.relativize(other)
755 def split(self, depth: int) -> Tuple["Name", "Name"]:
756 """Split a name into a prefix and suffix names at the specified depth.
758 *depth* is an ``int`` specifying the number of labels in the suffix
760 Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the
761 name.
763 Returns the tuple ``(prefix, suffix)``.
764 """
766 l = len(self.labels)
767 if depth == 0:
768 return (self, dns.name.empty)
769 elif depth == l:
770 return (dns.name.empty, self)
771 elif depth < 0 or depth > l:
772 raise ValueError("depth must be >= 0 and <= the length of the name")
773 return (Name(self[:-depth]), Name(self[-depth:]))
775 def concatenate(self, other: "Name") -> "Name":
776 """Return a new name which is the concatenation of self and other.
778 Raises ``dns.name.AbsoluteConcatenation`` if the name is
779 absolute and *other* is not the empty name.
781 Returns a ``dns.name.Name``.
782 """
784 if self.is_absolute() and len(other) > 0:
785 raise AbsoluteConcatenation
786 labels = list(self.labels)
787 labels.extend(list(other.labels))
788 return Name(labels)
790 def relativize(self, origin: "Name") -> "Name":
791 """If the name is a subdomain of *origin*, return a new name which is
792 the name relative to origin. Otherwise return the name.
794 For example, relativizing ``www.dnspython.org.`` to origin
795 ``dnspython.org.`` returns the name ``www``. Relativizing ``example.``
796 to origin ``dnspython.org.`` returns ``example.``.
798 Returns a ``dns.name.Name``.
799 """
801 if origin is not None and self.is_subdomain(origin):
802 return Name(self[: -len(origin)])
803 else:
804 return self
806 def derelativize(self, origin: "Name") -> "Name":
807 """If the name is a relative name, return a new name which is the
808 concatenation of the name and origin. Otherwise return the name.
810 For example, derelativizing ``www`` to origin ``dnspython.org.``
811 returns the name ``www.dnspython.org.``. Derelativizing ``example.``
812 to origin ``dnspython.org.`` returns ``example.``.
814 Returns a ``dns.name.Name``.
815 """
817 if not self.is_absolute():
818 return self.concatenate(origin)
819 else:
820 return self
822 def choose_relativity(
823 self, origin: Optional["Name"] = None, relativize: bool = True
824 ) -> "Name":
825 """Return a name with the relativity desired by the caller.
827 If *origin* is ``None``, then the name is returned.
828 Otherwise, if *relativize* is ``True`` the name is
829 relativized, and if *relativize* is ``False`` the name is
830 derelativized.
832 Returns a ``dns.name.Name``.
833 """
835 if origin:
836 if relativize:
837 return self.relativize(origin)
838 else:
839 return self.derelativize(origin)
840 else:
841 return self
843 def parent(self) -> "Name":
844 """Return the parent of the name.
846 For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``.
848 Raises ``dns.name.NoParent`` if the name is either the root name or the
849 empty name, and thus has no parent.
851 Returns a ``dns.name.Name``.
852 """
854 if self == root or self == empty:
855 raise NoParent
856 return Name(self.labels[1:])
858 def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
859 """Return the maximal predecessor of *name* in the DNSSEC ordering in the zone
860 whose origin is *origin*, or return the longest name under *origin* if the
861 name is origin (i.e. wrap around to the longest name, which may still be
862 *origin* due to length considerations.
864 The relativity of the name is preserved, so if this name is relative
865 then the method will return a relative name, and likewise if this name
866 is absolute then the predecessor will be absolute.
868 *prefix_ok* indicates if prefixing labels is allowed, and
869 defaults to ``True``. Normally it is good to allow this, but if computing
870 a maximal predecessor at a zone cut point then ``False`` must be specified.
871 """
872 return _handle_relativity_and_call(
873 _absolute_predecessor, self, origin, prefix_ok
874 )
876 def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name":
877 """Return the minimal successor of *name* in the DNSSEC ordering in the zone
878 whose origin is *origin*, or return *origin* if the successor cannot be
879 computed due to name length limitations.
881 Note that *origin* is returned in the "too long" cases because wrapping
882 around to the origin is how NSEC records express "end of the zone".
884 The relativity of the name is preserved, so if this name is relative
885 then the method will return a relative name, and likewise if this name
886 is absolute then the successor will be absolute.
888 *prefix_ok* indicates if prefixing a new minimal label is allowed, and
889 defaults to ``True``. Normally it is good to allow this, but if computing
890 a minimal successor at a zone cut point then ``False`` must be specified.
891 """
892 return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok)
895#: The root name, '.'
896root = Name([b""])
898#: The empty name.
899empty = Name([])
902def from_unicode(
903 text: str, origin: Optional[Name] = root, idna_codec: Optional[IDNACodec] = None
904) -> Name:
905 """Convert unicode text into a Name object.
907 Labels are encoded in IDN ACE form according to rules specified by
908 the IDNA codec.
910 *text*, a ``str``, is the text to convert into a name.
912 *origin*, a ``dns.name.Name``, specifies the origin to
913 append to non-absolute names. The default is the root name.
915 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
916 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
917 is used.
919 Returns a ``dns.name.Name``.
920 """
922 if not isinstance(text, str):
923 raise ValueError("input to from_unicode() must be a unicode string")
924 if not (origin is None or isinstance(origin, Name)):
925 raise ValueError("origin must be a Name or None")
926 labels = []
927 label = ""
928 escaping = False
929 edigits = 0
930 total = 0
931 if idna_codec is None:
932 idna_codec = IDNA_2003
933 if text == "@":
934 text = ""
935 if text:
936 if text in [".", "\u3002", "\uff0e", "\uff61"]:
937 return Name([b""]) # no Unicode "u" on this constant!
938 for c in text:
939 if escaping:
940 if edigits == 0:
941 if c.isdigit():
942 total = int(c)
943 edigits += 1
944 else:
945 label += c
946 escaping = False
947 else:
948 if not c.isdigit():
949 raise BadEscape
950 total *= 10
951 total += int(c)
952 edigits += 1
953 if edigits == 3:
954 escaping = False
955 label += chr(total)
956 elif c in [".", "\u3002", "\uff0e", "\uff61"]:
957 if len(label) == 0:
958 raise EmptyLabel
959 labels.append(idna_codec.encode(label))
960 label = ""
961 elif c == "\\":
962 escaping = True
963 edigits = 0
964 total = 0
965 else:
966 label += c
967 if escaping:
968 raise BadEscape
969 if len(label) > 0:
970 labels.append(idna_codec.encode(label))
971 else:
972 labels.append(b"")
974 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
975 labels.extend(list(origin.labels))
976 return Name(labels)
979def is_all_ascii(text: str) -> bool:
980 for c in text:
981 if ord(c) > 0x7F:
982 return False
983 return True
986def from_text(
987 text: Union[bytes, str],
988 origin: Optional[Name] = root,
989 idna_codec: Optional[IDNACodec] = None,
990) -> Name:
991 """Convert text into a Name object.
993 *text*, a ``bytes`` or ``str``, is the text to convert into a name.
995 *origin*, a ``dns.name.Name``, specifies the origin to
996 append to non-absolute names. The default is the root name.
998 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
999 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
1000 is used.
1002 Returns a ``dns.name.Name``.
1003 """
1005 if isinstance(text, str):
1006 if not is_all_ascii(text):
1007 # Some codepoint in the input text is > 127, so IDNA applies.
1008 return from_unicode(text, origin, idna_codec)
1009 # The input is all ASCII, so treat this like an ordinary non-IDNA
1010 # domain name. Note that "all ASCII" is about the input text,
1011 # not the codepoints in the domain name. E.g. if text has value
1012 #
1013 # r'\150\151\152\153\154\155\156\157\158\159'
1014 #
1015 # then it's still "all ASCII" even though the domain name has
1016 # codepoints > 127.
1017 text = text.encode("ascii")
1018 if not isinstance(text, bytes):
1019 raise ValueError("input to from_text() must be a string")
1020 if not (origin is None or isinstance(origin, Name)):
1021 raise ValueError("origin must be a Name or None")
1022 labels = []
1023 label = b""
1024 escaping = False
1025 edigits = 0
1026 total = 0
1027 if text == b"@":
1028 text = b""
1029 if text:
1030 if text == b".":
1031 return Name([b""])
1032 for c in text:
1033 byte_ = struct.pack("!B", c)
1034 if escaping:
1035 if edigits == 0:
1036 if byte_.isdigit():
1037 total = int(byte_)
1038 edigits += 1
1039 else:
1040 label += byte_
1041 escaping = False
1042 else:
1043 if not byte_.isdigit():
1044 raise BadEscape
1045 total *= 10
1046 total += int(byte_)
1047 edigits += 1
1048 if edigits == 3:
1049 escaping = False
1050 label += struct.pack("!B", total)
1051 elif byte_ == b".":
1052 if len(label) == 0:
1053 raise EmptyLabel
1054 labels.append(label)
1055 label = b""
1056 elif byte_ == b"\\":
1057 escaping = True
1058 edigits = 0
1059 total = 0
1060 else:
1061 label += byte_
1062 if escaping:
1063 raise BadEscape
1064 if len(label) > 0:
1065 labels.append(label)
1066 else:
1067 labels.append(b"")
1068 if (len(labels) == 0 or labels[-1] != b"") and origin is not None:
1069 labels.extend(list(origin.labels))
1070 return Name(labels)
1073# we need 'dns.wire.Parser' quoted as dns.name and dns.wire depend on each other.
1076def from_wire_parser(parser: "dns.wire.Parser") -> Name:
1077 """Convert possibly compressed wire format into a Name.
1079 *parser* is a dns.wire.Parser.
1081 Raises ``dns.name.BadPointer`` if a compression pointer did not
1082 point backwards in the message.
1084 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
1086 Returns a ``dns.name.Name``
1087 """
1089 labels = []
1090 biggest_pointer = parser.current
1091 with parser.restore_furthest():
1092 count = parser.get_uint8()
1093 while count != 0:
1094 if count < 64:
1095 labels.append(parser.get_bytes(count))
1096 elif count >= 192:
1097 current = (count & 0x3F) * 256 + parser.get_uint8()
1098 if current >= biggest_pointer:
1099 raise BadPointer
1100 biggest_pointer = current
1101 parser.seek(current)
1102 else:
1103 raise BadLabelType
1104 count = parser.get_uint8()
1105 labels.append(b"")
1106 return Name(labels)
1109def from_wire(message: bytes, current: int) -> Tuple[Name, int]:
1110 """Convert possibly compressed wire format into a Name.
1112 *message* is a ``bytes`` containing an entire DNS message in DNS
1113 wire form.
1115 *current*, an ``int``, is the offset of the beginning of the name
1116 from the start of the message
1118 Raises ``dns.name.BadPointer`` if a compression pointer did not
1119 point backwards in the message.
1121 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
1123 Returns a ``(dns.name.Name, int)`` tuple consisting of the name
1124 that was read and the number of bytes of the wire format message
1125 which were consumed reading it.
1126 """
1128 if not isinstance(message, bytes):
1129 raise ValueError("input to from_wire() must be a byte string")
1130 parser = dns.wire.Parser(message, current)
1131 name = from_wire_parser(parser)
1132 return (name, parser.current - current)
1135# RFC 4471 Support
1137_MINIMAL_OCTET = b"\x00"
1138_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET)
1139_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET])
1140_MAXIMAL_OCTET = b"\xff"
1141_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET)
1142_AT_SIGN_VALUE = ord("@")
1143_LEFT_SQUARE_BRACKET_VALUE = ord("[")
1146def _wire_length(labels):
1147 return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0)
1150def _pad_to_max_name(name):
1151 needed = 255 - _wire_length(name.labels)
1152 new_labels = []
1153 while needed > 64:
1154 new_labels.append(_MAXIMAL_OCTET * 63)
1155 needed -= 64
1156 if needed >= 2:
1157 new_labels.append(_MAXIMAL_OCTET * (needed - 1))
1158 # Note we're already maximal in the needed == 1 case as while we'd like
1159 # to add one more byte as a new label, we can't, as adding a new non-empty
1160 # label requires at least 2 bytes.
1161 new_labels = list(reversed(new_labels))
1162 new_labels.extend(name.labels)
1163 return Name(new_labels)
1166def _pad_to_max_label(label, suffix_labels):
1167 length = len(label)
1168 # We have to subtract one here to account for the length byte of label.
1169 remaining = 255 - _wire_length(suffix_labels) - length - 1
1170 if remaining <= 0:
1171 # Shouldn't happen!
1172 return label
1173 needed = min(63 - length, remaining)
1174 return label + _MAXIMAL_OCTET * needed
1177def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1178 # This is the RFC 4471 predecessor algorithm using the "absolute method" of section
1179 # 3.1.1.
1180 #
1181 # Our caller must ensure that the name and origin are absolute, and that name is a
1182 # subdomain of origin.
1183 if name == origin:
1184 return _pad_to_max_name(name)
1185 least_significant_label = name[0]
1186 if least_significant_label == _MINIMAL_OCTET:
1187 return name.parent()
1188 least_octet = least_significant_label[-1]
1189 suffix_labels = name.labels[1:]
1190 if least_octet == _MINIMAL_OCTET_VALUE:
1191 new_labels = [least_significant_label[:-1]]
1192 else:
1193 octets = bytearray(least_significant_label)
1194 octet = octets[-1]
1195 if octet == _LEFT_SQUARE_BRACKET_VALUE:
1196 octet = _AT_SIGN_VALUE
1197 else:
1198 octet -= 1
1199 octets[-1] = octet
1200 least_significant_label = bytes(octets)
1201 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)]
1202 new_labels.extend(suffix_labels)
1203 name = Name(new_labels)
1204 if prefix_ok:
1205 return _pad_to_max_name(name)
1206 else:
1207 return name
1210def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name:
1211 # This is the RFC 4471 successor algorithm using the "absolute method" of section
1212 # 3.1.2.
1213 #
1214 # Our caller must ensure that the name and origin are absolute, and that name is a
1215 # subdomain of origin.
1216 if prefix_ok:
1217 # Try prefixing \000 as new label
1218 try:
1219 return _SUCCESSOR_PREFIX.concatenate(name)
1220 except NameTooLong:
1221 pass
1222 while name != origin:
1223 # Try extending the least significant label.
1224 least_significant_label = name[0]
1225 if len(least_significant_label) < 63:
1226 # We may be able to extend the least label with a minimal additional byte.
1227 # This is only "may" because we could have a maximal length name even though
1228 # the least significant label isn't maximally long.
1229 new_labels = [least_significant_label + _MINIMAL_OCTET]
1230 new_labels.extend(name.labels[1:])
1231 try:
1232 return dns.name.Name(new_labels)
1233 except dns.name.NameTooLong:
1234 pass
1235 # We can't extend the label either, so we'll try to increment the least
1236 # signficant non-maximal byte in it.
1237 octets = bytearray(least_significant_label)
1238 # We do this reversed iteration with an explicit indexing variable because
1239 # if we find something to increment, we're going to want to truncate everything
1240 # to the right of it.
1241 for i in range(len(octets) - 1, -1, -1):
1242 octet = octets[i]
1243 if octet == _MAXIMAL_OCTET_VALUE:
1244 # We can't increment this, so keep looking.
1245 continue
1246 # Finally, something we can increment. We have to apply a special rule for
1247 # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when
1248 # comparing names, uppercase letters compare as if they were their
1249 # lower-case equivalents. If we increment "@" to "A", then it would compare
1250 # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have
1251 # skipped the most minimal successor, namely "[".
1252 if octet == _AT_SIGN_VALUE:
1253 octet = _LEFT_SQUARE_BRACKET_VALUE
1254 else:
1255 octet += 1
1256 octets[i] = octet
1257 # We can now truncate all of the maximal values we skipped (if any)
1258 new_labels = [bytes(octets[: i + 1])]
1259 new_labels.extend(name.labels[1:])
1260 # We haven't changed the length of the name, so the Name constructor will
1261 # always work.
1262 return Name(new_labels)
1263 # We couldn't increment, so chop off the least significant label and try
1264 # again.
1265 name = name.parent()
1267 # We couldn't increment at all, so return the origin, as wrapping around is the
1268 # DNSSEC way.
1269 return origin
1272def _handle_relativity_and_call(
1273 function: Callable[[Name, Name, bool], Name],
1274 name: Name,
1275 origin: Name,
1276 prefix_ok: bool,
1277) -> Name:
1278 # Make "name" absolute if needed, ensure that the origin is absolute,
1279 # call function(), and then relativize the result if needed.
1280 if not origin.is_absolute():
1281 raise NeedAbsoluteNameOrOrigin
1282 relative = not name.is_absolute()
1283 if relative:
1284 name = name.derelativize(origin)
1285 elif not name.is_subdomain(origin):
1286 raise NeedSubdomainOfOrigin
1287 result_name = function(name, origin, prefix_ok)
1288 if relative:
1289 result_name = result_name.relativize(origin)
1290 return result_name