Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dns/name.py: 27%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

623 statements  

1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license 

2 

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. 

17 

18"""DNS Names.""" 

19 

20import copy 

21import dataclasses 

22import encodings.idna # pyright: ignore 

23import functools 

24import struct 

25from collections.abc import Callable, Iterable 

26from typing import Any 

27 

28import dns._features 

29import dns.enum 

30import dns.exception 

31import dns.immutable 

32import dns.wirebase 

33from dns.style import BaseStyle 

34 

35# Dnspython will never access idna if the import fails, but pyright can't figure 

36# that out, so... 

37# 

38# pyright: reportAttributeAccessIssue = false, reportPossiblyUnboundVariable = false 

39 

40if dns._features.have("idna"): 

41 import idna # pyright: ignore 

42 

43 have_idna_2008 = True 

44else: # pragma: no cover 

45 have_idna_2008 = False 

46 

47 

48CompressType = dict["Name", int] 

49 

50 

51class NameRelation(dns.enum.IntEnum): 

52 """Name relation result from fullcompare().""" 

53 

54 # This is an IntEnum for backwards compatibility in case anyone 

55 # has hardwired the constants. 

56 

57 #: The compared names have no relationship to each other. 

58 NONE = 0 

59 #: the first name is a superdomain of the second. 

60 SUPERDOMAIN = 1 

61 #: The first name is a subdomain of the second. 

62 SUBDOMAIN = 2 

63 #: The compared names are equal. 

64 EQUAL = 3 

65 #: The compared names have a common ancestor. 

66 COMMONANCESTOR = 4 

67 

68 @classmethod 

69 def _maximum(cls): 

70 return cls.COMMONANCESTOR # pragma: no cover 

71 

72 @classmethod 

73 def _short_name(cls): 

74 return cls.__name__ # pragma: no cover 

75 

76 

77# Backwards compatibility 

78NAMERELN_NONE = NameRelation.NONE 

79NAMERELN_SUPERDOMAIN = NameRelation.SUPERDOMAIN 

80NAMERELN_SUBDOMAIN = NameRelation.SUBDOMAIN 

81NAMERELN_EQUAL = NameRelation.EQUAL 

82NAMERELN_COMMONANCESTOR = NameRelation.COMMONANCESTOR 

83 

84 

85class EmptyLabel(dns.exception.SyntaxError): 

86 """A DNS label is empty.""" 

87 

88 

89class BadEscape(dns.exception.SyntaxError): 

90 """An escaped code in a text format of DNS name is invalid.""" 

91 

92 

93class BadPointer(dns.exception.FormError): 

94 """A DNS compression pointer points forward instead of backward.""" 

95 

96 

97class BadLabelType(dns.exception.FormError): 

98 """The label type in DNS name wire format is unknown.""" 

99 

100 

101class NeedAbsoluteNameOrOrigin(dns.exception.DNSException): 

102 """An attempt was made to convert a non-absolute name to 

103 wire when there was also a non-absolute (or missing) origin.""" 

104 

105 

106class NameTooLong(dns.exception.FormError): 

107 """A DNS name is > 255 octets long.""" 

108 

109 

110class LabelTooLong(dns.exception.SyntaxError): 

111 """A DNS label is > 63 octets long.""" 

112 

113 

114class AbsoluteConcatenation(dns.exception.DNSException): 

115 """An attempt was made to append anything other than the 

116 empty name to an absolute DNS name.""" 

117 

118 

119class NoParent(dns.exception.DNSException): 

120 """An attempt was made to get the parent of the root name 

121 or the empty name.""" 

122 

123 

124class NoIDNA2008(dns.exception.DNSException): 

125 """IDNA 2008 processing was requested but the idna module is not 

126 available.""" 

127 

128 

129class IDNAException(dns.exception.DNSException): 

130 """IDNA processing raised an exception.""" 

131 

132 supp_kwargs = {"idna_exception"} 

133 fmt = "IDNA processing exception: {idna_exception}" 

134 

135 # We do this as otherwise mypy complains about unexpected keyword argument 

136 # idna_exception 

137 def __init__(self, *args, **kwargs): 

138 super().__init__(*args, **kwargs) 

139 

140 

141class NeedSubdomainOfOrigin(dns.exception.DNSException): 

142 """An absolute name was provided that is not a subdomain of the specified origin.""" 

143 

144 

145_escaped = b'"().;\\@$' 

146_escaped_text = '"().;\\@$' 

147 

148 

149def _escapify(label: bytes | str) -> str: 

150 """Escape the characters in label which need it. 

151 

152 :returns: the escaped string 

153 :rtype: str 

154 """ 

155 if isinstance(label, bytes): 

156 # Ordinary DNS label mode. Escape special characters and values 

157 # < 0x20 or > 0x7f. 

158 text = "" 

159 for c in label: 

160 if c in _escaped: 

161 text += "\\" + chr(c) 

162 elif c > 0x20 and c < 0x7F: 

163 text += chr(c) 

164 else: 

165 text += f"\\{c:03d}" 

166 return text 

167 

168 # Unicode label mode. Escape only special characters and values < 0x20 

169 text = "" 

170 for uc in label: 

171 if uc in _escaped_text: 

172 text += "\\" + uc 

173 elif uc <= "\x20": 

174 text += f"\\{ord(uc):03d}" 

175 else: 

176 text += uc 

177 return text 

178 

179 

180class IDNACodec: 

181 """Abstract base class for IDNA encoder/decoders.""" 

182 

183 def __init__(self): 

184 pass 

185 

186 def is_idna(self, label: bytes) -> bool: 

187 return label.lower().startswith(b"xn--") 

188 

189 def encode(self, label: str) -> bytes: 

190 raise NotImplementedError # pragma: no cover 

191 

192 def decode(self, label: bytes) -> str: 

193 # We do not apply any IDNA policy on decode. 

194 if self.is_idna(label): 

195 try: 

196 slabel = label[4:].decode("punycode") 

197 return _escapify(slabel) 

198 except Exception as e: 

199 raise IDNAException(idna_exception=e) 

200 else: 

201 return _escapify(label) 

202 

203 

204class IDNA2003Codec(IDNACodec): 

205 """IDNA 2003 encoder/decoder.""" 

206 

207 def __init__(self, strict_decode: bool = False): 

208 """Initialize the IDNA 2003 encoder/decoder. 

209 

210 :param bool strict_decode: If ``True``, then IDNA2003 checking 

211 is done when decoding. This can cause failures if the name 

212 was encoded with IDNA2008. The default is ``False``. 

213 """ 

214 

215 super().__init__() 

216 self.strict_decode = strict_decode 

217 

218 def encode(self, label: str) -> bytes: 

219 """Encode *label*.""" 

220 

221 if label == "": 

222 return b"" 

223 try: 

224 return encodings.idna.ToASCII(label) 

225 except UnicodeError: 

226 raise LabelTooLong 

227 

228 def decode(self, label: bytes) -> str: 

229 """Decode *label*.""" 

230 if not self.strict_decode: 

231 return super().decode(label) 

232 if label == b"": 

233 return "" 

234 try: 

235 return _escapify(encodings.idna.ToUnicode(label)) 

236 except Exception as e: 

237 raise IDNAException(idna_exception=e) 

238 

239 

240class IDNA2008Codec(IDNACodec): 

241 """IDNA 2008 encoder/decoder.""" 

242 

243 def __init__( 

244 self, 

245 uts_46: bool = False, 

246 transitional: bool = False, 

247 allow_pure_ascii: bool = False, 

248 strict_decode: bool = False, 

249 ): 

250 """Initialize the IDNA 2008 encoder/decoder. 

251 

252 :param bool uts_46: If ``True``, apply Unicode IDNA compatibility 

253 processing as described in Unicode Technical Standard #46 

254 (https://unicode.org/reports/tr46/). If ``False``, do not 

255 apply the mapping. The default is ``False``. 

256 :param bool transitional: If ``True``, use the "transitional" mode 

257 described in Unicode Technical Standard #46. The default is 

258 ``False``. This setting has no effect in idna 3.11 and later 

259 as transitional support has been removed. 

260 :param bool allow_pure_ascii: If ``True``, then a label which 

261 consists of only ASCII characters is allowed. This is less 

262 strict than regular IDNA 2008, but is also necessary for mixed 

263 names, e.g. a name starting with ``_sip._tcp.`` and ending 

264 in an IDN suffix which would otherwise be disallowed. The 

265 default is ``False``. 

266 :param bool strict_decode: If ``True``, then IDNA2008 checking 

267 is done when decoding. This can cause failures if the name 

268 was encoded with IDNA2003. The default is ``False``. 

269 """ 

270 super().__init__() 

271 self.uts_46 = uts_46 

272 self.transitional = transitional 

273 self.allow_pure_ascii = allow_pure_ascii 

274 self.strict_decode = strict_decode 

275 

276 def encode(self, label: str) -> bytes: 

277 if label == "": 

278 return b"" 

279 if self.allow_pure_ascii and is_all_ascii(label): 

280 encoded = label.encode("ascii") 

281 if len(encoded) > 63: 

282 raise LabelTooLong 

283 return encoded 

284 if not have_idna_2008: 

285 raise NoIDNA2008 

286 try: 

287 if self.uts_46: 

288 # pylint: disable=possibly-used-before-assignment 

289 label = idna.uts46_remap(label, False, self.transitional) 

290 return idna.alabel(label) 

291 except idna.IDNAError as e: 

292 if e.args[0] == "Label too long": 

293 raise LabelTooLong 

294 else: 

295 raise IDNAException(idna_exception=e) 

296 

297 def decode(self, label: bytes) -> str: 

298 if not self.strict_decode: 

299 return super().decode(label) 

300 if label == b"": 

301 return "" 

302 if not have_idna_2008: 

303 raise NoIDNA2008 

304 try: 

305 ulabel = idna.ulabel(label) 

306 if self.uts_46: 

307 ulabel = idna.uts46_remap(ulabel, False, self.transitional) 

308 return _escapify(ulabel) 

309 except (idna.IDNAError, UnicodeError) as e: 

310 raise IDNAException(idna_exception=e) 

311 

312 

313IDNA_2003_Practical = IDNA2003Codec(False) 

314IDNA_2003_Strict = IDNA2003Codec(True) 

315IDNA_2003 = IDNA_2003_Practical 

316IDNA_2008_Practical = IDNA2008Codec(True, False, True, False) 

317IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False) 

318IDNA_2008_Strict = IDNA2008Codec(False, False, False, True) 

319IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False) 

320IDNA_2008 = IDNA_2008_Practical 

321if have_idna_2008: 

322 IDNA_DEFAULT = IDNA_2008_Practical 

323else: 

324 IDNA_DEFAULT = IDNA_2003_Practical 

325 

326 

327def set_default_idna_codec(idna_codec: IDNACodec): 

328 """Set the default IDNA codec.""" 

329 global IDNA_DEFAULT 

330 IDNA_DEFAULT = idna_codec 

331 

332 

333def _validate_labels(labels: tuple[bytes, ...]) -> None: 

334 """Check for empty labels in the middle of a label sequence, 

335 labels that are too long, and for too many labels. 

336 

337 :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. 

341 """ 

342 

343 l = len(labels) 

344 total = 0 

345 i = -1 

346 j = 0 

347 for label in labels: 

348 ll = len(label) 

349 total += ll + 1 

350 if ll > 63: 

351 raise LabelTooLong 

352 if i < 0 and label == b"": 

353 i = j 

354 j += 1 

355 if total > 255: 

356 raise NameTooLong 

357 if i >= 0 and i != l - 1: 

358 raise EmptyLabel 

359 

360 

361def _maybe_convert_to_binary(label: bytes | str) -> bytes: 

362 """If label is ``str``, convert it to ``bytes``. If it is already 

363 ``bytes`` just return it. 

364 

365 """ 

366 

367 if isinstance(label, bytes): 

368 return label 

369 else: 

370 return label.encode() 

371 

372 

373@dataclasses.dataclass(frozen=True) 

374class NameStyle(BaseStyle): 

375 """Name text styles. 

376 

377 :param bool omit_final_dot: If ``True``, don't emit the final dot 

378 (denoting the root label) for absolute names. The default is 

379 ``False``. 

380 :param idna_codec: The IDNA decoder to use. The default is ``None``, 

381 which means all text is in the standard DNS zonefile format, i.e. 

382 punycode will not be decoded. 

383 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None`` 

384 :param origin: If ``None`` (the default), the name's relativity is not 

385 altered before conversion to text. Otherwise, if *relativize* is 

386 ``True`` the name is relativized, and if *relativize* is ``False`` 

387 the name is derelativized. 

388 :type origin: :py:class:`dns.name.Name` or ``None`` 

389 :param bool relativize: Controls the direction of relativization when 

390 *origin* is set. 

391 """ 

392 

393 omit_final_dot: bool = False 

394 idna_codec: IDNACodec | None = None 

395 origin: "Name | None" = None 

396 relativize: bool = False 

397 

398 

399@dns.immutable.immutable 

400class Name: 

401 """A DNS name. 

402 

403 The dns.name.Name class represents a DNS name as a tuple of 

404 labels. Each label is a ``bytes`` in DNS wire format. Instances 

405 of the class are immutable. 

406 """ 

407 

408 __slots__ = ["labels"] 

409 

410 def __init__(self, labels: Iterable[bytes | str]): 

411 """Initialize a DNS name. 

412 

413 :param labels: An iterable whose values are ``str`` or ``bytes``. 

414 """ 

415 

416 blabels = [_maybe_convert_to_binary(x) for x in labels] 

417 self.labels = tuple(blabels) 

418 _validate_labels(self.labels) 

419 

420 def __copy__(self): 

421 return Name(self.labels) 

422 

423 def __deepcopy__(self, memo): 

424 return Name(copy.deepcopy(self.labels, memo)) 

425 

426 def __getstate__(self): 

427 # Names can be pickled 

428 return {"labels": self.labels} 

429 

430 def __setstate__(self, state): 

431 super().__setattr__("labels", state["labels"]) 

432 _validate_labels(self.labels) 

433 

434 def is_absolute(self) -> bool: 

435 """Is the most significant label of this name the root label? 

436 

437 :rtype: bool 

438 """ 

439 

440 return len(self.labels) > 0 and self.labels[-1] == b"" 

441 

442 def is_wild(self) -> bool: 

443 """Is this name wild? (I.e. Is the least significant label ``'*'``?) 

444 

445 :rtype: bool 

446 """ 

447 

448 return len(self.labels) > 0 and self.labels[0] == b"*" 

449 

450 def __hash__(self) -> int: 

451 """Return a case-insensitive hash of the name. 

452 

453 :rtype: int 

454 """ 

455 

456 h = 0 

457 for label in self.labels: 

458 for c in label.lower(): 

459 h += (h << 3) + c 

460 return h 

461 

462 def fullcompare(self, other: "Name") -> tuple[NameRelation, int, int]: 

463 """Compare two names, returning a 3-tuple 

464 ``(relation, order, nlabels)``. 

465 

466 *relation* describes the relationship between the names, and is one 

467 of: :py:attr:`dns.name.NameRelation.NONE`, 

468 :py:attr:`dns.name.NameRelation.SUPERDOMAIN`, 

469 :py:attr:`dns.name.NameRelation.SUBDOMAIN`, 

470 :py:attr:`dns.name.NameRelation.EQUAL`, or 

471 :py:attr:`dns.name.NameRelation.COMMONANCESTOR`. 

472 

473 *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and == 

474 0 if *self* == *other*. A relative name is always less than an 

475 absolute name. If both names have the same relativity, then 

476 the DNSSEC order relation is used to order them. 

477 

478 *nlabels* is the number of significant labels that the two names 

479 have in common. 

480 

481 Here are some examples. Names ending in "." are absolute names, 

482 those not ending in "." are relative names. 

483 

484 ============= ============= =========== ===== ======= 

485 self other relation order nlabels 

486 ============= ============= =========== ===== ======= 

487 www.example. www.example. equal 0 3 

488 www.example. example. subdomain > 0 2 

489 example. www.example. superdomain < 0 2 

490 example1.com. example2.com. common anc. < 0 2 

491 example1 example2. none < 0 0 

492 example1. example2 none > 0 0 

493 ============= ============= =========== ===== ======= 

494 

495 :param other: The name to compare with. 

496 :type other: :py:class:`dns.name.Name` 

497 :returns: A 3-tuple ``(relation, order, nlabels)``. 

498 :rtype: tuple[:py:class:`dns.name.NameRelation`, int, int] 

499 """ 

500 

501 sabs = self.is_absolute() 

502 oabs = other.is_absolute() 

503 if sabs != oabs: 

504 if sabs: 

505 return (NameRelation.NONE, 1, 0) 

506 else: 

507 return (NameRelation.NONE, -1, 0) 

508 l1 = len(self.labels) 

509 l2 = len(other.labels) 

510 ldiff = l1 - l2 

511 if ldiff < 0: 

512 l = l1 

513 else: 

514 l = l2 

515 

516 order = 0 

517 nlabels = 0 

518 namereln = NameRelation.NONE 

519 while l > 0: 

520 l -= 1 

521 l1 -= 1 

522 l2 -= 1 

523 label1 = self.labels[l1].lower() 

524 label2 = other.labels[l2].lower() 

525 if label1 < label2: 

526 order = -1 

527 if nlabels > 0: 

528 namereln = NameRelation.COMMONANCESTOR 

529 return (namereln, order, nlabels) 

530 elif label1 > label2: 

531 order = 1 

532 if nlabels > 0: 

533 namereln = NameRelation.COMMONANCESTOR 

534 return (namereln, order, nlabels) 

535 nlabels += 1 

536 order = ldiff 

537 if ldiff < 0: 

538 namereln = NameRelation.SUPERDOMAIN 

539 elif ldiff > 0: 

540 namereln = NameRelation.SUBDOMAIN 

541 else: 

542 namereln = NameRelation.EQUAL 

543 return (namereln, order, nlabels) 

544 

545 def is_subdomain(self, other: "Name") -> bool: 

546 """Is self a subdomain of other? 

547 

548 Note that the notion of subdomain includes equality, e.g. 

549 ``dnspython.org`` is a subdomain of itself. 

550 

551 :rtype: bool 

552 """ 

553 

554 nr, _, _ = self.fullcompare(other) 

555 if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL: 

556 return True 

557 return False 

558 

559 def is_superdomain(self, other: "Name") -> bool: 

560 """Is self a superdomain of other? 

561 

562 Note that the notion of superdomain includes equality, e.g. 

563 ``dnspython.org`` is a superdomain of itself. 

564 

565 :rtype: bool 

566 """ 

567 

568 nr, _, _ = self.fullcompare(other) 

569 if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL: 

570 return True 

571 return False 

572 

573 def canonicalize(self) -> "Name": 

574 """Return a name which is equal to the current name, but is in 

575 DNSSEC canonical form. 

576 

577 :rtype: :py:class:`dns.name.Name` 

578 """ 

579 

580 return Name([x.lower() for x in self.labels]) 

581 

582 def __eq__(self, other): 

583 if isinstance(other, Name): 

584 return self.fullcompare(other)[1] == 0 

585 else: 

586 return False 

587 

588 def __ne__(self, other): 

589 if isinstance(other, Name): 

590 return self.fullcompare(other)[1] != 0 

591 else: 

592 return True 

593 

594 def __lt__(self, other): 

595 if isinstance(other, Name): 

596 return self.fullcompare(other)[1] < 0 

597 else: 

598 return NotImplemented 

599 

600 def __le__(self, other): 

601 if isinstance(other, Name): 

602 return self.fullcompare(other)[1] <= 0 

603 else: 

604 return NotImplemented 

605 

606 def __ge__(self, other): 

607 if isinstance(other, Name): 

608 return self.fullcompare(other)[1] >= 0 

609 else: 

610 return NotImplemented 

611 

612 def __gt__(self, other): 

613 if isinstance(other, Name): 

614 return self.fullcompare(other)[1] > 0 

615 else: 

616 return NotImplemented 

617 

618 def __repr__(self): 

619 return "<DNS name " + self.__str__() + ">" 

620 

621 def __str__(self): 

622 return self.to_text(False) 

623 

624 def to_text( 

625 self, omit_final_dot: bool = False, style: NameStyle | None = None 

626 ) -> str: 

627 """Convert name to DNS text format. 

628 

629 :param bool omit_final_dot: If ``True``, don't emit the final dot 

630 (denoting the root label) for absolute names. The default is 

631 ``False``. 

632 :param style: If specified, the style overrides the other parameters. 

633 :type style: :py:class:`dns.name.NameStyle` or ``None`` 

634 :rtype: str 

635 """ 

636 if style is None: 

637 style = NameStyle(omit_final_dot=omit_final_dot) 

638 return self.to_styled_text(style) 

639 

640 def to_unicode( 

641 self, 

642 omit_final_dot: bool = False, 

643 idna_codec: IDNACodec | None = None, 

644 style: NameStyle | None = None, 

645 ) -> str: 

646 """Convert name to DNS text format. 

647 

648 IDN ACE labels are converted to Unicode using the specified codec. 

649 

650 :param bool omit_final_dot: If ``True``, don't emit the final dot 

651 (denoting the root label) for absolute names. The default is 

652 ``False``. 

653 :param idna_codec: The IDNA codec to use for decoding ACE labels. 

654 If ``None``, the default IDNA codec is used. 

655 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None`` 

656 :param style: If specified, the style overrides the other parameters. 

657 :type style: :py:class:`dns.name.NameStyle` or ``None`` 

658 :rtype: str 

659 """ 

660 if idna_codec is None: 

661 idna_codec = IDNA_DEFAULT 

662 if style is None: 

663 style = NameStyle(omit_final_dot=omit_final_dot, idna_codec=idna_codec) 

664 return self.to_styled_text(style) 

665 

666 def to_styled_text(self, style: NameStyle) -> str: 

667 """Convert name to text format, applying the style. 

668 

669 See the documentation for :py:class:`dns.name.NameStyle` for a description 

670 of the style parameters. 

671 

672 :param style: The style to apply. 

673 :type style: :py:class:`dns.name.NameStyle` 

674 :rtype: str 

675 """ 

676 

677 name = self.choose_relativity(style.origin, style.relativize) 

678 if len(name.labels) == 0: 

679 return "@" 

680 if len(name.labels) == 1 and name.labels[0] == b"": 

681 return "." 

682 if style.omit_final_dot and name.is_absolute(): 

683 l = name.labels[:-1] 

684 else: 

685 l = name.labels 

686 if style.idna_codec is None: 

687 return ".".join(map(_escapify, l)) 

688 else: 

689 return ".".join([style.idna_codec.decode(x) for x in l]) 

690 

691 def to_digestable(self, origin: "Name | None" = None) -> bytes: 

692 """Convert name to a format suitable for digesting in hashes. 

693 

694 The name is canonicalized and converted to uncompressed wire 

695 format. All names in wire format are absolute. If the name 

696 is a relative name, then an origin must be supplied. 

697 

698 :param origin: If the name is relative and *origin* is not ``None``, 

699 then *origin* will be appended to the name. 

700 :type origin: :py:class:`dns.name.Name` or ``None`` 

701 :raises dns.name.NeedAbsoluteNameOrOrigin: if the name is relative 

702 and no origin was provided. 

703 :rtype: bytes 

704 """ 

705 

706 digest = self.to_wire(origin=origin, canonicalize=True) 

707 assert digest is not None 

708 return digest 

709 

710 def to_wire( 

711 self, 

712 file: Any | None = None, 

713 compress: CompressType | None = None, 

714 origin: "Name | None" = None, 

715 canonicalize: bool = False, 

716 ) -> bytes | None: 

717 """Convert name to wire format, possibly compressing it. 

718 

719 :param file: The file where the name is emitted (typically an 

720 ``io.BytesIO`` file). If ``None`` (the default), a ``bytes`` 

721 containing the wire name will be returned. 

722 :param compress: The compression table to use. If ``None`` (the 

723 default), names will not be compressed. Note that the compression 

724 code assumes that compression offset 0 is the start of *file*, 

725 and thus compression will not be correct if this is not the case. 

726 :type compress: dict or ``None`` 

727 :param origin: If the name is relative and *origin* is not ``None``, 

728 then *origin* will be appended to it. 

729 :type origin: :py:class:`dns.name.Name` or ``None`` 

730 :param bool canonicalize: Whether the name should be canonicalized; 

731 that is, converted to a format suitable for digesting in hashes. 

732 :raises dns.name.NeedAbsoluteNameOrOrigin: if the name is relative 

733 and no origin was provided. 

734 :returns: ``None`` if *file* is provided, otherwise the wire format 

735 as ``bytes``. 

736 :rtype: bytes or ``None`` 

737 """ 

738 

739 if file is None: 

740 out = bytearray() 

741 for label in self.labels: 

742 out.append(len(label)) 

743 if canonicalize: 

744 out += label.lower() 

745 else: 

746 out += label 

747 if not self.is_absolute(): 

748 if origin is None or not origin.is_absolute(): 

749 raise NeedAbsoluteNameOrOrigin 

750 for label in origin.labels: 

751 out.append(len(label)) 

752 if canonicalize: 

753 out += label.lower() 

754 else: 

755 out += label 

756 return bytes(out) 

757 

758 labels: Iterable[bytes] 

759 if not self.is_absolute(): 

760 if origin is None or not origin.is_absolute(): 

761 raise NeedAbsoluteNameOrOrigin 

762 labels = list(self.labels) 

763 labels.extend(list(origin.labels)) 

764 else: 

765 labels = self.labels 

766 i = 0 

767 for label in labels: 

768 n = Name(labels[i:]) 

769 i += 1 

770 if compress is not None: 

771 pos = compress.get(n) 

772 else: 

773 pos = None 

774 if pos is not None: 

775 value = 0xC000 + pos 

776 s = struct.pack("!H", value) 

777 file.write(s) 

778 break 

779 else: 

780 if compress is not None and len(n) > 1: 

781 pos = file.tell() 

782 if pos <= 0x3FFF: 

783 compress[n] = pos 

784 l = len(label) 

785 file.write(struct.pack("!B", l)) 

786 if l > 0: 

787 if canonicalize: 

788 file.write(label.lower()) 

789 else: 

790 file.write(label) 

791 return None 

792 

793 def __len__(self) -> int: 

794 """The length of the name (in labels). 

795 

796 :rtype: int 

797 """ 

798 

799 return len(self.labels) 

800 

801 def __getitem__(self, index: Any) -> Any: 

802 return self.labels[index] 

803 

804 def __add__(self, other): 

805 return self.concatenate(other) 

806 

807 def __sub__(self, other): 

808 return self.relativize(other) 

809 

810 def split(self, depth: int) -> tuple["Name", "Name"]: 

811 """Split a name into a prefix and suffix names at the specified depth. 

812 

813 :param int depth: The number of labels in the suffix. 

814 :raises ValueError: if *depth* is not >= 0 and <= the length of the 

815 name. 

816 :returns: A ``(prefix, suffix)`` tuple. 

817 :rtype: tuple[:py:class:`dns.name.Name`, :py:class:`dns.name.Name`] 

818 """ 

819 

820 l = len(self.labels) 

821 if depth == 0: 

822 return (self, empty) 

823 elif depth == l: 

824 return (empty, self) 

825 elif depth < 0 or depth > l: 

826 raise ValueError("depth must be >= 0 and <= the length of the name") 

827 return (Name(self[:-depth]), Name(self[-depth:])) 

828 

829 def concatenate(self, other: "Name") -> "Name": 

830 """Return a new name which is the concatenation of self and other. 

831 

832 :param other: The name to concatenate. 

833 :type other: :py:class:`dns.name.Name` 

834 :raises dns.name.AbsoluteConcatenation: if the name is absolute and 

835 *other* is not the empty name. 

836 :rtype: :py:class:`dns.name.Name` 

837 """ 

838 

839 if self.is_absolute() and len(other) > 0: 

840 raise AbsoluteConcatenation 

841 labels = list(self.labels) 

842 labels.extend(list(other.labels)) 

843 return Name(labels) 

844 

845 def relativize(self, origin: "Name") -> "Name": 

846 """If the name is a subdomain of *origin*, return a new name which is 

847 the name relative to origin. Otherwise return the name. 

848 

849 For example, relativizing ``www.dnspython.org.`` to origin 

850 ``dnspython.org.`` returns the name ``www``. Relativizing ``example.`` 

851 to origin ``dnspython.org.`` returns ``example.``. 

852 

853 :param origin: The origin to relativize against. 

854 :type origin: :py:class:`dns.name.Name` 

855 :rtype: :py:class:`dns.name.Name` 

856 """ 

857 

858 if self.is_subdomain(origin): 

859 return Name(self[: -len(origin)]) 

860 else: 

861 return self 

862 

863 def derelativize(self, origin: "Name") -> "Name": 

864 """If the name is a relative name, return a new name which is the 

865 concatenation of the name and origin. Otherwise return the name. 

866 

867 For example, derelativizing ``www`` to origin ``dnspython.org.`` 

868 returns the name ``www.dnspython.org.``. Derelativizing ``example.`` 

869 to origin ``dnspython.org.`` returns ``example.``. 

870 

871 :param origin: The origin to append. 

872 :type origin: :py:class:`dns.name.Name` 

873 :rtype: :py:class:`dns.name.Name` 

874 """ 

875 

876 if not self.is_absolute(): 

877 return self.concatenate(origin) 

878 else: 

879 return self 

880 

881 def choose_relativity( 

882 self, origin: "Name | None" = None, relativize: bool = True 

883 ) -> "Name": 

884 """Return a name with the relativity desired by the caller. 

885 

886 If *origin* is ``None``, then the name is returned. 

887 Otherwise, if *relativize* is ``True`` the name is 

888 relativized, and if *relativize* is ``False`` the name is 

889 derelativized. 

890 

891 :param origin: If not ``None``, controls relativization. 

892 :type origin: :py:class:`dns.name.Name` or ``None`` 

893 :param bool relativize: If ``True``, relativize; if ``False``, 

894 derelativize. 

895 :rtype: :py:class:`dns.name.Name` 

896 """ 

897 

898 if origin: 

899 if relativize: 

900 return self.relativize(origin) 

901 else: 

902 return self.derelativize(origin) 

903 else: 

904 return self 

905 

906 def parent(self) -> "Name": 

907 """Return the parent of the name. 

908 

909 For example, the parent of ``www.dnspython.org.`` is ``dnspython.org.``. 

910 

911 :raises dns.name.NoParent: if the name is either the root name or the 

912 empty name, and thus has no parent. 

913 :rtype: :py:class:`dns.name.Name` 

914 """ 

915 

916 if self == root or self == empty: 

917 raise NoParent 

918 return Name(self.labels[1:]) 

919 

920 def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name": 

921 """Return the maximal predecessor of the name in the DNSSEC ordering. 

922 

923 Returns the maximal predecessor in the zone whose origin is *origin*, 

924 or returns the longest name under *origin* if the name is *origin* 

925 (i.e. wraps around to the longest name, which may still be *origin* 

926 due to length considerations). 

927 

928 The relativity of the name is preserved, so if this name is relative 

929 then the method will return a relative name, and likewise if this name 

930 is absolute then the predecessor will be absolute. 

931 

932 :param origin: The zone origin. 

933 :type origin: :py:class:`dns.name.Name` 

934 :param bool prefix_ok: If ``True`` (the default), prefixing labels is 

935 allowed. Specify ``False`` when computing a maximal predecessor 

936 at a zone cut point. 

937 :rtype: :py:class:`dns.name.Name` 

938 """ 

939 return _handle_relativity_and_call( 

940 _absolute_predecessor, self, origin, prefix_ok 

941 ) 

942 

943 def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name": 

944 """Return the minimal successor of the name in the DNSSEC ordering. 

945 

946 Returns the minimal successor in the zone whose origin is *origin*, 

947 or returns *origin* if the successor cannot be computed due to name 

948 length limitations. 

949 

950 Note that *origin* is returned in the "too long" cases because wrapping 

951 around to the origin is how NSEC records express "end of the zone". 

952 

953 The relativity of the name is preserved, so if this name is relative 

954 then the method will return a relative name, and likewise if this name 

955 is absolute then the successor will be absolute. 

956 

957 :param origin: The zone origin. 

958 :type origin: :py:class:`dns.name.Name` 

959 :param bool prefix_ok: If ``True`` (the default), prefixing a new 

960 minimal label is allowed. Specify ``False`` when computing a 

961 minimal successor at a zone cut point. 

962 :rtype: :py:class:`dns.name.Name` 

963 """ 

964 return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok) 

965 

966 

967#: The root name, '.' 

968root = Name([b""]) 

969 

970#: The empty name. 

971empty = Name([]) 

972 

973 

974def from_unicode( 

975 text: str, origin: Name | None = root, idna_codec: IDNACodec | None = None 

976) -> Name: 

977 """Convert unicode text into a :py:class:`dns.name.Name` object. 

978 

979 Labels are encoded in IDN ACE form according to rules specified by 

980 the IDNA codec. 

981 

982 :param str text: The text to convert into a name. 

983 :param origin: The origin to append to non-absolute names. The 

984 default is the root name. 

985 :type origin: :py:class:`dns.name.Name` or ``None`` 

986 :param idna_codec: The IDNA encoder/decoder. If ``None``, the default 

987 IDNA encoder/decoder is used. 

988 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None`` 

989 :rtype: :py:class:`dns.name.Name` 

990 """ 

991 

992 labels = [] 

993 label = "" 

994 escaping = False 

995 edigits = 0 

996 total = 0 

997 if idna_codec is None: 

998 idna_codec = IDNA_DEFAULT 

999 if text == "@": 

1000 text = "" 

1001 if text: 

1002 if text in [".", "\u3002", "\uff0e", "\uff61"]: 

1003 return Name([b""]) # no Unicode "u" on this constant! 

1004 for c in text: 

1005 if escaping: 

1006 if edigits == 0: 

1007 if c.isdigit(): 

1008 total = int(c) 

1009 edigits += 1 

1010 else: 

1011 label += c 

1012 escaping = False 

1013 else: 

1014 if not c.isdigit(): 

1015 raise BadEscape 

1016 total *= 10 

1017 total += int(c) 

1018 edigits += 1 

1019 if edigits == 3: 

1020 escaping = False 

1021 label += chr(total) 

1022 elif c in [".", "\u3002", "\uff0e", "\uff61"]: 

1023 if len(label) == 0: 

1024 raise EmptyLabel 

1025 labels.append(idna_codec.encode(label)) 

1026 label = "" 

1027 elif c == "\\": 

1028 escaping = True 

1029 edigits = 0 

1030 total = 0 

1031 else: 

1032 label += c 

1033 if escaping: 

1034 raise BadEscape 

1035 if len(label) > 0: 

1036 labels.append(idna_codec.encode(label)) 

1037 else: 

1038 labels.append(b"") 

1039 

1040 if (len(labels) == 0 or labels[-1] != b"") and origin is not None: 

1041 labels.extend(list(origin.labels)) 

1042 return Name(labels) 

1043 

1044 

1045def is_all_ascii(text: str) -> bool: 

1046 for c in text: 

1047 if ord(c) > 0x7F: 

1048 return False 

1049 return True 

1050 

1051 

1052def from_text( 

1053 text: bytes | str, 

1054 origin: Name | None = root, 

1055 idna_codec: IDNACodec | None = None, 

1056) -> Name: 

1057 """Convert text into a :py:class:`dns.name.Name` object. 

1058 

1059 :param text: The text to convert into a name. 

1060 :type text: bytes or str 

1061 :param origin: The origin to append to non-absolute names. The 

1062 default is the root name. 

1063 :type origin: :py:class:`dns.name.Name` or ``None`` 

1064 :param idna_codec: The IDNA encoder/decoder. If ``None``, the default 

1065 IDNA encoder/decoder is used. 

1066 :type idna_codec: :py:class:`dns.name.IDNACodec` or ``None`` 

1067 :rtype: :py:class:`dns.name.Name` 

1068 """ 

1069 

1070 if isinstance(text, str): 

1071 if not is_all_ascii(text): 

1072 # Some codepoint in the input text is > 127, so IDNA applies. 

1073 return from_unicode(text, origin, idna_codec) 

1074 # The input is all ASCII, so treat this like an ordinary non-IDNA 

1075 # domain name. Note that "all ASCII" is about the input text, 

1076 # not the codepoints in the domain name. E.g. if text has value 

1077 # 

1078 # r'\150\151\152\153\154\155\156\157\158\159' 

1079 # 

1080 # then it's still "all ASCII" even though the domain name has 

1081 # codepoints > 127. 

1082 text = text.encode("ascii") 

1083 labels = [] 

1084 label = b"" 

1085 escaping = False 

1086 edigits = 0 

1087 total = 0 

1088 if text == b"@": 

1089 text = b"" 

1090 if text: 

1091 if text == b".": 

1092 return Name([b""]) 

1093 for c in text: 

1094 byte_ = struct.pack("!B", c) 

1095 if escaping: 

1096 if edigits == 0: 

1097 if byte_.isdigit(): 

1098 total = int(byte_) 

1099 edigits += 1 

1100 else: 

1101 label += byte_ 

1102 escaping = False 

1103 else: 

1104 if not byte_.isdigit(): 

1105 raise BadEscape 

1106 total *= 10 

1107 total += int(byte_) 

1108 edigits += 1 

1109 if edigits == 3: 

1110 escaping = False 

1111 label += struct.pack("!B", total) 

1112 elif byte_ == b".": 

1113 if len(label) == 0: 

1114 raise EmptyLabel 

1115 labels.append(label) 

1116 label = b"" 

1117 elif byte_ == b"\\": 

1118 escaping = True 

1119 edigits = 0 

1120 total = 0 

1121 else: 

1122 label += byte_ 

1123 if escaping: 

1124 raise BadEscape 

1125 if len(label) > 0: 

1126 labels.append(label) 

1127 else: 

1128 labels.append(b"") 

1129 if (len(labels) == 0 or labels[-1] != b"") and origin is not None: 

1130 labels.extend(list(origin.labels)) 

1131 return Name(labels) 

1132 

1133 

1134def from_wire_parser(parser: dns.wirebase.Parser) -> Name: 

1135 """Convert possibly compressed wire format into a :py:class:`dns.name.Name`. 

1136 

1137 :param parser: The wire format parser. 

1138 :type parser: :py:class:`dns.wirebase.Parser` 

1139 :raises dns.name.BadPointer: if a compression pointer did not 

1140 point backwards in the message. 

1141 :raises dns.name.BadLabelType: if an invalid label type was encountered. 

1142 :rtype: :py:class:`dns.name.Name` 

1143 """ 

1144 

1145 labels = [] 

1146 biggest_pointer = parser.current 

1147 with parser.restore_furthest(): 

1148 count = parser.get_uint8() 

1149 while count != 0: 

1150 if count < 64: 

1151 labels.append(parser.get_bytes(count)) 

1152 elif count >= 192: 

1153 current = (count & 0x3F) * 256 + parser.get_uint8() 

1154 if current >= biggest_pointer: 

1155 raise BadPointer 

1156 biggest_pointer = current 

1157 parser.seek(current) 

1158 else: 

1159 raise BadLabelType 

1160 count = parser.get_uint8() 

1161 labels.append(b"") 

1162 return Name(labels) 

1163 

1164 

1165def from_wire(message: bytes, current: int) -> tuple[Name, int]: 

1166 """Convert possibly compressed wire format into a :py:class:`dns.name.Name`. 

1167 

1168 :param bytes message: A ``bytes`` containing an entire DNS message in DNS 

1169 wire form. 

1170 :param int current: The offset of the beginning of the name from the 

1171 start of the message. 

1172 :raises dns.name.BadPointer: if a compression pointer did not 

1173 point backwards in the message. 

1174 :raises dns.name.BadLabelType: if an invalid label type was encountered. 

1175 :returns: A tuple of the name that was read and the number of bytes of 

1176 the wire format message which were consumed reading it. 

1177 :rtype: tuple[:py:class:`dns.name.Name`, int] 

1178 """ 

1179 

1180 parser = dns.wirebase.Parser(message, current) 

1181 name = from_wire_parser(parser) 

1182 return (name, parser.current - current) 

1183 

1184 

1185# RFC 4471 Support 

1186 

1187_MINIMAL_OCTET = b"\x00" 

1188_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET) 

1189_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET]) 

1190_MAXIMAL_OCTET = b"\xff" 

1191_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET) 

1192_AT_SIGN_VALUE = ord("@") 

1193_LEFT_SQUARE_BRACKET_VALUE = ord("[") 

1194 

1195 

1196def _wire_length(labels): 

1197 return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0) 

1198 

1199 

1200def _pad_to_max_name(name): 

1201 needed = 255 - _wire_length(name.labels) 

1202 new_labels = [] 

1203 while needed > 64: 

1204 new_labels.append(_MAXIMAL_OCTET * 63) 

1205 needed -= 64 

1206 if needed >= 2: 

1207 new_labels.append(_MAXIMAL_OCTET * (needed - 1)) 

1208 # Note we're already maximal in the needed == 1 case as while we'd like 

1209 # to add one more byte as a new label, we can't, as adding a new non-empty 

1210 # label requires at least 2 bytes. 

1211 new_labels = list(reversed(new_labels)) 

1212 new_labels.extend(name.labels) 

1213 return Name(new_labels) 

1214 

1215 

1216def _pad_to_max_label(label, suffix_labels): 

1217 length = len(label) 

1218 # We have to subtract one here to account for the length byte of label. 

1219 remaining = 255 - _wire_length(suffix_labels) - length - 1 

1220 if remaining <= 0: 

1221 # Shouldn't happen! 

1222 return label 

1223 needed = min(63 - length, remaining) 

1224 return label + _MAXIMAL_OCTET * needed 

1225 

1226 

1227def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name: 

1228 # This is the RFC 4471 predecessor algorithm using the "absolute method" of section 

1229 # 3.1.1. 

1230 # 

1231 # Our caller must ensure that the name and origin are absolute, and that name is a 

1232 # subdomain of origin. 

1233 if name == origin: 

1234 return _pad_to_max_name(name) 

1235 least_significant_label = name[0] 

1236 if least_significant_label == _MINIMAL_OCTET: 

1237 return name.parent() 

1238 least_octet = least_significant_label[-1] 

1239 suffix_labels = name.labels[1:] 

1240 if least_octet == _MINIMAL_OCTET_VALUE: 

1241 new_labels = [least_significant_label[:-1]] 

1242 else: 

1243 octets = bytearray(least_significant_label) 

1244 octet = octets[-1] 

1245 if octet == _LEFT_SQUARE_BRACKET_VALUE: 

1246 octet = _AT_SIGN_VALUE 

1247 else: 

1248 octet -= 1 

1249 octets[-1] = octet 

1250 least_significant_label = bytes(octets) 

1251 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)] 

1252 new_labels.extend(suffix_labels) 

1253 name = Name(new_labels) 

1254 if prefix_ok: 

1255 return _pad_to_max_name(name) 

1256 else: 

1257 return name 

1258 

1259 

1260def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name: 

1261 # This is the RFC 4471 successor algorithm using the "absolute method" of section 

1262 # 3.1.2. 

1263 # 

1264 # Our caller must ensure that the name and origin are absolute, and that name is a 

1265 # subdomain of origin. 

1266 if prefix_ok: 

1267 # Try prefixing \000 as new label 

1268 try: 

1269 return _SUCCESSOR_PREFIX.concatenate(name) 

1270 except NameTooLong: 

1271 pass 

1272 while name != origin: 

1273 # Try extending the least significant label. 

1274 least_significant_label = name[0] 

1275 if len(least_significant_label) < 63: 

1276 # We may be able to extend the least label with a minimal additional byte. 

1277 # This is only "may" because we could have a maximal length name even though 

1278 # the least significant label isn't maximally long. 

1279 new_labels = [least_significant_label + _MINIMAL_OCTET] 

1280 new_labels.extend(name.labels[1:]) 

1281 try: 

1282 return Name(new_labels) 

1283 except NameTooLong: 

1284 pass 

1285 # We can't extend the label either, so we'll try to increment the least 

1286 # signficant non-maximal byte in it. 

1287 octets = bytearray(least_significant_label) 

1288 # We do this reversed iteration with an explicit indexing variable because 

1289 # if we find something to increment, we're going to want to truncate everything 

1290 # to the right of it. 

1291 for i in range(len(octets) - 1, -1, -1): 

1292 octet = octets[i] 

1293 if octet == _MAXIMAL_OCTET_VALUE: 

1294 # We can't increment this, so keep looking. 

1295 continue 

1296 # Finally, something we can increment. We have to apply a special rule for 

1297 # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when 

1298 # comparing names, uppercase letters compare as if they were their 

1299 # lower-case equivalents. If we increment "@" to "A", then it would compare 

1300 # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have 

1301 # skipped the most minimal successor, namely "[". 

1302 if octet == _AT_SIGN_VALUE: 

1303 octet = _LEFT_SQUARE_BRACKET_VALUE 

1304 else: 

1305 octet += 1 

1306 octets[i] = octet 

1307 # We can now truncate all of the maximal values we skipped (if any) 

1308 new_labels = [bytes(octets[: i + 1])] 

1309 new_labels.extend(name.labels[1:]) 

1310 # We haven't changed the length of the name, so the Name constructor will 

1311 # always work. 

1312 return Name(new_labels) 

1313 # We couldn't increment, so chop off the least significant label and try 

1314 # again. 

1315 name = name.parent() 

1316 

1317 # We couldn't increment at all, so return the origin, as wrapping around is the 

1318 # DNSSEC way. 

1319 return origin 

1320 

1321 

1322def _handle_relativity_and_call( 

1323 function: Callable[[Name, Name, bool], Name], 

1324 name: Name, 

1325 origin: Name, 

1326 prefix_ok: bool, 

1327) -> Name: 

1328 # Make "name" absolute if needed, ensure that the origin is absolute, 

1329 # call function(), and then relativize the result if needed. 

1330 if not origin.is_absolute(): 

1331 raise NeedAbsoluteNameOrOrigin 

1332 relative = not name.is_absolute() 

1333 if relative: 

1334 name = name.derelativize(origin) 

1335 elif not name.is_subdomain(origin): 

1336 raise NeedSubdomainOfOrigin 

1337 result_name = function(name, origin, prefix_ok) 

1338 if relative: 

1339 result_name = result_name.relativize(origin) 

1340 return result_name