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 @returns: the escaped string 

152 @rtype: string""" 

153 if isinstance(label, bytes): 

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

155 # < 0x20 or > 0x7f. 

156 text = "" 

157 for c in label: 

158 if c in _escaped: 

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

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

161 text += chr(c) 

162 else: 

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

164 return text 

165 

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

167 text = "" 

168 for uc in label: 

169 if uc in _escaped_text: 

170 text += "\\" + uc 

171 elif uc <= "\x20": 

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

173 else: 

174 text += uc 

175 return text 

176 

177 

178class IDNACodec: 

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

180 

181 def __init__(self): 

182 pass 

183 

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

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

186 

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

188 raise NotImplementedError # pragma: no cover 

189 

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

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

192 if self.is_idna(label): 

193 try: 

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

195 return _escapify(slabel) 

196 except Exception as e: 

197 raise IDNAException(idna_exception=e) 

198 else: 

199 return _escapify(label) 

200 

201 

202class IDNA2003Codec(IDNACodec): 

203 """IDNA 2003 encoder/decoder.""" 

204 

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

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

207 

208 *strict_decode* is a ``bool``. If `True`, then IDNA2003 checking 

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

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

211 """ 

212 

213 super().__init__() 

214 self.strict_decode = strict_decode 

215 

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

217 """Encode *label*.""" 

218 

219 if label == "": 

220 return b"" 

221 try: 

222 return encodings.idna.ToASCII(label) 

223 except UnicodeError: 

224 raise LabelTooLong 

225 

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

227 """Decode *label*.""" 

228 if not self.strict_decode: 

229 return super().decode(label) 

230 if label == b"": 

231 return "" 

232 try: 

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

234 except Exception as e: 

235 raise IDNAException(idna_exception=e) 

236 

237 

238class IDNA2008Codec(IDNACodec): 

239 """IDNA 2008 encoder/decoder.""" 

240 

241 def __init__( 

242 self, 

243 uts_46: bool = False, 

244 transitional: bool = False, 

245 allow_pure_ascii: bool = False, 

246 strict_decode: bool = False, 

247 ): 

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

249 

250 *uts_46* is a ``bool``. If True, apply Unicode IDNA 

251 compatibility processing as described in Unicode Technical 

252 Standard #46 (https://unicode.org/reports/tr46/). 

253 If False, do not apply the mapping. The default is False. 

254 

255 *transitional* is a ``bool``: If True, use the 

256 "transitional" mode described in Unicode Technical Standard 

257 #46. The default is False. This setting has no effect 

258 in idna 3.11 and later as transitional support has been removed. 

259 

260 *allow_pure_ascii* is a ``bool``. 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 with starting with "_sip._tcp." and ending 

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

265 default is False. 

266 

267 *strict_decode* is a ``bool``: If True, then IDNA2008 checking 

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

269 was encoded with IDNA2003. The default is False. 

270 """ 

271 super().__init__() 

272 self.uts_46 = uts_46 

273 self.transitional = transitional 

274 self.allow_pure_ascii = allow_pure_ascii 

275 self.strict_decode = strict_decode 

276 

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

278 if label == "": 

279 return b"" 

280 if self.allow_pure_ascii and is_all_ascii(label): 

281 encoded = label.encode("ascii") 

282 if len(encoded) > 63: 

283 raise LabelTooLong 

284 return encoded 

285 if not have_idna_2008: 

286 raise NoIDNA2008 

287 try: 

288 if self.uts_46: 

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

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

291 return idna.alabel(label) 

292 except idna.IDNAError as e: 

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

294 raise LabelTooLong 

295 else: 

296 raise IDNAException(idna_exception=e) 

297 

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

299 if not self.strict_decode: 

300 return super().decode(label) 

301 if label == b"": 

302 return "" 

303 if not have_idna_2008: 

304 raise NoIDNA2008 

305 try: 

306 ulabel = idna.ulabel(label) 

307 if self.uts_46: 

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

309 return _escapify(ulabel) 

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

311 raise IDNAException(idna_exception=e) 

312 

313 

314IDNA_2003_Practical = IDNA2003Codec(False) 

315IDNA_2003_Strict = IDNA2003Codec(True) 

316IDNA_2003 = IDNA_2003_Practical 

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

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

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

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

321IDNA_2008 = IDNA_2008_Practical 

322if have_idna_2008: 

323 IDNA_DEFAULT = IDNA_2008_Practical 

324else: 

325 IDNA_DEFAULT = IDNA_2003_Practical 

326 

327 

328def set_default_idna_codec(idna_codec: IDNACodec): 

329 """Set the default IDNA codec.""" 

330 global IDNA_DEFAULT 

331 IDNA_DEFAULT = idna_codec 

332 

333 

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

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

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

337 

338 Raises ``dns.name.NameTooLong`` if the name as a whole is too long. 

339 

340 Raises ``dns.name.EmptyLabel`` if a label is empty (i.e. the root 

341 label) and appears in a position other than the end of the label 

342 sequence 

343 

344 """ 

345 

346 l = len(labels) 

347 total = 0 

348 i = -1 

349 j = 0 

350 for label in labels: 

351 ll = len(label) 

352 total += ll + 1 

353 if ll > 63: 

354 raise LabelTooLong 

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

356 i = j 

357 j += 1 

358 if total > 255: 

359 raise NameTooLong 

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

361 raise EmptyLabel 

362 

363 

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

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

366 ``bytes`` just return it. 

367 

368 """ 

369 

370 if isinstance(label, bytes): 

371 return label 

372 else: 

373 return label.encode() 

374 

375 

376@dataclasses.dataclass(frozen=True) 

377class NameStyle(BaseStyle): 

378 """Name text styles 

379 

380 *omit_final_dot* is a ``bool``. If True, don't emit the final 

381 dot (denoting the root label) for absolute names. The default 

382 is False. 

383 

384 *idna_codec* specifies the IDNA decoder to use. The default is ``None`` 

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

386 punycode will not be decoded. 

387 

388 If *origin* is ``None``, the default, then the name's relativity is not 

389 altered before conversion to text. Otherwise, if *relativize* is ``True`` 

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

391 derelativized. 

392 """ 

393 

394 omit_final_dot: bool = False 

395 idna_codec: IDNACodec | None = None 

396 origin: "Name | None" = None 

397 relativize: bool = False 

398 

399 

400@dns.immutable.immutable 

401class Name: 

402 """A DNS name. 

403 

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

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

406 of the class are immutable. 

407 """ 

408 

409 __slots__ = ["labels"] 

410 

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

412 """*labels* is any iterable whose values are ``str`` or ``bytes``.""" 

413 

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

415 self.labels = tuple(blabels) 

416 _validate_labels(self.labels) 

417 

418 def __copy__(self): 

419 return Name(self.labels) 

420 

421 def __deepcopy__(self, memo): 

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

423 

424 def __getstate__(self): 

425 # Names can be pickled 

426 return {"labels": self.labels} 

427 

428 def __setstate__(self, state): 

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

430 _validate_labels(self.labels) 

431 

432 def is_absolute(self) -> bool: 

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

434 

435 Returns a ``bool``. 

436 """ 

437 

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

439 

440 def is_wild(self) -> bool: 

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

442 

443 Returns a ``bool``. 

444 """ 

445 

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

447 

448 def __hash__(self) -> int: 

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

450 

451 Returns an ``int``. 

452 """ 

453 

454 h = 0 

455 for label in self.labels: 

456 for c in label.lower(): 

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

458 return h 

459 

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

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

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

463 

464 *relation* describes the relation ship between the names, 

465 and is one of: ``dns.name.NameRelation.NONE``, 

466 ``dns.name.NameRelation.SUPERDOMAIN``, ``dns.name.NameRelation.SUBDOMAIN``, 

467 ``dns.name.NameRelation.EQUAL``, or ``dns.name.NameRelation.COMMONANCESTOR``. 

468 

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

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

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

472 the DNSSEC order relation is used to order them. 

473 

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

475 have in common. 

476 

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

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

479 

480 ============= ============= =========== ===== ======= 

481 self other relation order nlabels 

482 ============= ============= =========== ===== ======= 

483 www.example. www.example. equal 0 3 

484 www.example. example. subdomain > 0 2 

485 example. www.example. superdomain < 0 2 

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

487 example1 example2. none < 0 0 

488 example1. example2 none > 0 0 

489 ============= ============= =========== ===== ======= 

490 """ 

491 

492 sabs = self.is_absolute() 

493 oabs = other.is_absolute() 

494 if sabs != oabs: 

495 if sabs: 

496 return (NameRelation.NONE, 1, 0) 

497 else: 

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

499 l1 = len(self.labels) 

500 l2 = len(other.labels) 

501 ldiff = l1 - l2 

502 if ldiff < 0: 

503 l = l1 

504 else: 

505 l = l2 

506 

507 order = 0 

508 nlabels = 0 

509 namereln = NameRelation.NONE 

510 while l > 0: 

511 l -= 1 

512 l1 -= 1 

513 l2 -= 1 

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

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

516 if label1 < label2: 

517 order = -1 

518 if nlabels > 0: 

519 namereln = NameRelation.COMMONANCESTOR 

520 return (namereln, order, nlabels) 

521 elif label1 > label2: 

522 order = 1 

523 if nlabels > 0: 

524 namereln = NameRelation.COMMONANCESTOR 

525 return (namereln, order, nlabels) 

526 nlabels += 1 

527 order = ldiff 

528 if ldiff < 0: 

529 namereln = NameRelation.SUPERDOMAIN 

530 elif ldiff > 0: 

531 namereln = NameRelation.SUBDOMAIN 

532 else: 

533 namereln = NameRelation.EQUAL 

534 return (namereln, order, nlabels) 

535 

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

537 """Is self a subdomain of other? 

538 

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

540 "dnspython.org" is a subdomain of itself. 

541 

542 Returns a ``bool``. 

543 """ 

544 

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

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

547 return True 

548 return False 

549 

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

551 """Is self a superdomain of other? 

552 

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

554 "dnspython.org" is a superdomain of itself. 

555 

556 Returns a ``bool``. 

557 """ 

558 

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

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

561 return True 

562 return False 

563 

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

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

566 DNSSEC canonical form. 

567 """ 

568 

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

570 

571 def __eq__(self, other): 

572 if isinstance(other, Name): 

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

574 else: 

575 return False 

576 

577 def __ne__(self, other): 

578 if isinstance(other, Name): 

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

580 else: 

581 return True 

582 

583 def __lt__(self, other): 

584 if isinstance(other, Name): 

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

586 else: 

587 return NotImplemented 

588 

589 def __le__(self, other): 

590 if isinstance(other, Name): 

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

592 else: 

593 return NotImplemented 

594 

595 def __ge__(self, other): 

596 if isinstance(other, Name): 

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

598 else: 

599 return NotImplemented 

600 

601 def __gt__(self, other): 

602 if isinstance(other, Name): 

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

604 else: 

605 return NotImplemented 

606 

607 def __repr__(self): 

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

609 

610 def __str__(self): 

611 return self.to_text(False) 

612 

613 def to_text( 

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

615 ) -> str: 

616 """Convert name to DNS text format. 

617 

618 *omit_final_dot* is a ``bool``. If True, don't emit the final 

619 dot (denoting the root label) for absolute names. The default 

620 is False. 

621 

622 *style*, a :py:class:`dns.name.NameStyle` or ``None`` (the default). If 

623 specified, the style overrides the other parameters. 

624 

625 Returns a ``str``. 

626 """ 

627 if style is None: 

628 style = NameStyle(omit_final_dot=omit_final_dot) 

629 return self.to_styled_text(style) 

630 

631 def to_unicode( 

632 self, 

633 omit_final_dot: bool = False, 

634 idna_codec: IDNACodec | None = None, 

635 style: NameStyle | None = None, 

636 ) -> str: 

637 """Convert name to DNS text format. 

638 

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

640 

641 *omit_final_dot* is a ``bool``. If True, don't emit the final 

642 dot (denoting the root label) for absolute names. The default 

643 is False. 

644 

645 Returns a ``str``. 

646 """ 

647 if idna_codec is None: 

648 idna_codec = IDNA_DEFAULT 

649 if style is None: 

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

651 return self.to_styled_text(style) 

652 

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

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

655 

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

657 of the style parameters. 

658 

659 Returns a ``str``. 

660 """ 

661 

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

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

664 return "@" 

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

666 return "." 

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

668 l = name.labels[:-1] 

669 else: 

670 l = name.labels 

671 if style.idna_codec is None: 

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

673 else: 

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

675 

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

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

678 

679 The name is canonicalized and converted to uncompressed wire 

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

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

682 

683 *origin* is a ``dns.name.Name`` or ``None``. If the name is 

684 relative and origin is not ``None``, then origin will be appended 

685 to the name. 

686 

687 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is 

688 relative and no origin was provided. 

689 

690 Returns a ``bytes``. 

691 """ 

692 

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

694 assert digest is not None 

695 return digest 

696 

697 def to_wire( 

698 self, 

699 file: Any | None = None, 

700 compress: CompressType | None = None, 

701 origin: "Name | None" = None, 

702 canonicalize: bool = False, 

703 ) -> bytes | None: 

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

705 

706 *file* is the file where the name is emitted (typically an 

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

708 containing the wire name will be returned. 

709 

710 *compress*, a ``dict``, is the compression table to use. If 

711 ``None`` (the default), names will not be compressed. Note that 

712 the compression code assumes that compression offset 0 is the 

713 start of *file*, and thus compression will not be correct 

714 if this is not the case. 

715 

716 *origin* is a ``dns.name.Name`` or ``None``. If the name is 

717 relative and origin is not ``None``, then *origin* will be appended 

718 to it. 

719 

720 *canonicalize*, a ``bool``, indicates whether the name should 

721 be canonicalized; that is, converted to a format suitable for 

722 digesting in hashes. 

723 

724 Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is 

725 relative and no origin was provided. 

726 

727 Returns a ``bytes`` or ``None``. 

728 """ 

729 

730 if file is None: 

731 out = bytearray() 

732 for label in self.labels: 

733 out.append(len(label)) 

734 if canonicalize: 

735 out += label.lower() 

736 else: 

737 out += label 

738 if not self.is_absolute(): 

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

740 raise NeedAbsoluteNameOrOrigin 

741 for label in origin.labels: 

742 out.append(len(label)) 

743 if canonicalize: 

744 out += label.lower() 

745 else: 

746 out += label 

747 return bytes(out) 

748 

749 labels: Iterable[bytes] 

750 if not self.is_absolute(): 

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

752 raise NeedAbsoluteNameOrOrigin 

753 labels = list(self.labels) 

754 labels.extend(list(origin.labels)) 

755 else: 

756 labels = self.labels 

757 i = 0 

758 for label in labels: 

759 n = Name(labels[i:]) 

760 i += 1 

761 if compress is not None: 

762 pos = compress.get(n) 

763 else: 

764 pos = None 

765 if pos is not None: 

766 value = 0xC000 + pos 

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

768 file.write(s) 

769 break 

770 else: 

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

772 pos = file.tell() 

773 if pos <= 0x3FFF: 

774 compress[n] = pos 

775 l = len(label) 

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

777 if l > 0: 

778 if canonicalize: 

779 file.write(label.lower()) 

780 else: 

781 file.write(label) 

782 return None 

783 

784 def __len__(self) -> int: 

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

786 

787 Returns an ``int``. 

788 """ 

789 

790 return len(self.labels) 

791 

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

793 return self.labels[index] 

794 

795 def __add__(self, other): 

796 return self.concatenate(other) 

797 

798 def __sub__(self, other): 

799 return self.relativize(other) 

800 

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

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

803 

804 *depth* is an ``int`` specifying the number of labels in the suffix 

805 

806 Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the 

807 name. 

808 

809 Returns the tuple ``(prefix, suffix)``. 

810 """ 

811 

812 l = len(self.labels) 

813 if depth == 0: 

814 return (self, empty) 

815 elif depth == l: 

816 return (empty, self) 

817 elif depth < 0 or depth > l: 

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

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

820 

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

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

823 

824 Raises ``dns.name.AbsoluteConcatenation`` if the name is 

825 absolute and *other* is not the empty name. 

826 

827 Returns a ``dns.name.Name``. 

828 """ 

829 

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

831 raise AbsoluteConcatenation 

832 labels = list(self.labels) 

833 labels.extend(list(other.labels)) 

834 return Name(labels) 

835 

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

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

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

839 

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

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

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

843 

844 Returns a ``dns.name.Name``. 

845 """ 

846 

847 if self.is_subdomain(origin): 

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

849 else: 

850 return self 

851 

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

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

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

855 

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

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

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

859 

860 Returns a ``dns.name.Name``. 

861 """ 

862 

863 if not self.is_absolute(): 

864 return self.concatenate(origin) 

865 else: 

866 return self 

867 

868 def choose_relativity( 

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

870 ) -> "Name": 

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

872 

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

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

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

876 derelativized. 

877 

878 Returns a ``dns.name.Name``. 

879 """ 

880 

881 if origin: 

882 if relativize: 

883 return self.relativize(origin) 

884 else: 

885 return self.derelativize(origin) 

886 else: 

887 return self 

888 

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

890 """Return the parent of the name. 

891 

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

893 

894 Raises ``dns.name.NoParent`` if the name is either the root name or the 

895 empty name, and thus has no parent. 

896 

897 Returns a ``dns.name.Name``. 

898 """ 

899 

900 if self == root or self == empty: 

901 raise NoParent 

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

903 

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

905 """Return the maximal predecessor of *name* in the DNSSEC ordering in the zone 

906 whose origin is *origin*, or return the longest name under *origin* if the 

907 name is origin (i.e. wrap around to the longest name, which may still be 

908 *origin* due to length considerations. 

909 

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

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

912 is absolute then the predecessor will be absolute. 

913 

914 *prefix_ok* indicates if prefixing labels is allowed, and 

915 defaults to ``True``. Normally it is good to allow this, but if computing 

916 a maximal predecessor at a zone cut point then ``False`` must be specified. 

917 """ 

918 return _handle_relativity_and_call( 

919 _absolute_predecessor, self, origin, prefix_ok 

920 ) 

921 

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

923 """Return the minimal successor of *name* in the DNSSEC ordering in the zone 

924 whose origin is *origin*, or return *origin* if the successor cannot be 

925 computed due to name length limitations. 

926 

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

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

929 

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

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

932 is absolute then the successor will be absolute. 

933 

934 *prefix_ok* indicates if prefixing a new minimal label is allowed, and 

935 defaults to ``True``. Normally it is good to allow this, but if computing 

936 a minimal successor at a zone cut point then ``False`` must be specified. 

937 """ 

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

939 

940 

941#: The root name, '.' 

942root = Name([b""]) 

943 

944#: The empty name. 

945empty = Name([]) 

946 

947 

948def from_unicode( 

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

950) -> Name: 

951 """Convert unicode text into a Name object. 

952 

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

954 the IDNA codec. 

955 

956 *text*, a ``str``, is the text to convert into a name. 

957 

958 *origin*, a ``dns.name.Name``, specifies the origin to 

959 append to non-absolute names. The default is the root name. 

960 

961 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA 

962 encoder/decoder. If ``None``, the default IDNA encoder/decoder 

963 is used. 

964 

965 Returns a ``dns.name.Name``. 

966 """ 

967 

968 labels = [] 

969 label = "" 

970 escaping = False 

971 edigits = 0 

972 total = 0 

973 if idna_codec is None: 

974 idna_codec = IDNA_DEFAULT 

975 if text == "@": 

976 text = "" 

977 if text: 

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

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

980 for c in text: 

981 if escaping: 

982 if edigits == 0: 

983 if c.isdigit(): 

984 total = int(c) 

985 edigits += 1 

986 else: 

987 label += c 

988 escaping = False 

989 else: 

990 if not c.isdigit(): 

991 raise BadEscape 

992 total *= 10 

993 total += int(c) 

994 edigits += 1 

995 if edigits == 3: 

996 escaping = False 

997 label += chr(total) 

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

999 if len(label) == 0: 

1000 raise EmptyLabel 

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

1002 label = "" 

1003 elif c == "\\": 

1004 escaping = True 

1005 edigits = 0 

1006 total = 0 

1007 else: 

1008 label += c 

1009 if escaping: 

1010 raise BadEscape 

1011 if len(label) > 0: 

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

1013 else: 

1014 labels.append(b"") 

1015 

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

1017 labels.extend(list(origin.labels)) 

1018 return Name(labels) 

1019 

1020 

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

1022 for c in text: 

1023 if ord(c) > 0x7F: 

1024 return False 

1025 return True 

1026 

1027 

1028def from_text( 

1029 text: bytes | str, 

1030 origin: Name | None = root, 

1031 idna_codec: IDNACodec | None = None, 

1032) -> Name: 

1033 """Convert text into a Name object. 

1034 

1035 *text*, a ``bytes`` or ``str``, is the text to convert into a name. 

1036 

1037 *origin*, a ``dns.name.Name``, specifies the origin to 

1038 append to non-absolute names. The default is the root name. 

1039 

1040 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA 

1041 encoder/decoder. If ``None``, the default IDNA encoder/decoder 

1042 is used. 

1043 

1044 Returns a ``dns.name.Name``. 

1045 """ 

1046 

1047 if isinstance(text, str): 

1048 if not is_all_ascii(text): 

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

1050 return from_unicode(text, origin, idna_codec) 

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

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

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

1054 # 

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

1056 # 

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

1058 # codepoints > 127. 

1059 text = text.encode("ascii") 

1060 labels = [] 

1061 label = b"" 

1062 escaping = False 

1063 edigits = 0 

1064 total = 0 

1065 if text == b"@": 

1066 text = b"" 

1067 if text: 

1068 if text == b".": 

1069 return Name([b""]) 

1070 for c in text: 

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

1072 if escaping: 

1073 if edigits == 0: 

1074 if byte_.isdigit(): 

1075 total = int(byte_) 

1076 edigits += 1 

1077 else: 

1078 label += byte_ 

1079 escaping = False 

1080 else: 

1081 if not byte_.isdigit(): 

1082 raise BadEscape 

1083 total *= 10 

1084 total += int(byte_) 

1085 edigits += 1 

1086 if edigits == 3: 

1087 escaping = False 

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

1089 elif byte_ == b".": 

1090 if len(label) == 0: 

1091 raise EmptyLabel 

1092 labels.append(label) 

1093 label = b"" 

1094 elif byte_ == b"\\": 

1095 escaping = True 

1096 edigits = 0 

1097 total = 0 

1098 else: 

1099 label += byte_ 

1100 if escaping: 

1101 raise BadEscape 

1102 if len(label) > 0: 

1103 labels.append(label) 

1104 else: 

1105 labels.append(b"") 

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

1107 labels.extend(list(origin.labels)) 

1108 return Name(labels) 

1109 

1110 

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

1112 """Convert possibly compressed wire format into a Name. 

1113 

1114 *parser* is a dns.wirebase.Parser. 

1115 

1116 Raises ``dns.name.BadPointer`` if a compression pointer did not 

1117 point backwards in the message. 

1118 

1119 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered. 

1120 

1121 Returns a ``dns.name.Name`` 

1122 """ 

1123 

1124 labels = [] 

1125 biggest_pointer = parser.current 

1126 with parser.restore_furthest(): 

1127 count = parser.get_uint8() 

1128 while count != 0: 

1129 if count < 64: 

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

1131 elif count >= 192: 

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

1133 if current >= biggest_pointer: 

1134 raise BadPointer 

1135 biggest_pointer = current 

1136 parser.seek(current) 

1137 else: 

1138 raise BadLabelType 

1139 count = parser.get_uint8() 

1140 labels.append(b"") 

1141 return Name(labels) 

1142 

1143 

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

1145 """Convert possibly compressed wire format into a Name. 

1146 

1147 *message* is a ``bytes`` containing an entire DNS message in DNS 

1148 wire form. 

1149 

1150 *current*, an ``int``, is the offset of the beginning of the name 

1151 from the start of the message 

1152 

1153 Raises ``dns.name.BadPointer`` if a compression pointer did not 

1154 point backwards in the message. 

1155 

1156 Raises ``dns.name.BadLabelType`` if an invalid label type was encountered. 

1157 

1158 Returns a ``(dns.name.Name, int)`` tuple consisting of the name 

1159 that was read and the number of bytes of the wire format message 

1160 which were consumed reading it. 

1161 """ 

1162 

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

1164 name = from_wire_parser(parser) 

1165 return (name, parser.current - current) 

1166 

1167 

1168# RFC 4471 Support 

1169 

1170_MINIMAL_OCTET = b"\x00" 

1171_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET) 

1172_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET]) 

1173_MAXIMAL_OCTET = b"\xff" 

1174_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET) 

1175_AT_SIGN_VALUE = ord("@") 

1176_LEFT_SQUARE_BRACKET_VALUE = ord("[") 

1177 

1178 

1179def _wire_length(labels): 

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

1181 

1182 

1183def _pad_to_max_name(name): 

1184 needed = 255 - _wire_length(name.labels) 

1185 new_labels = [] 

1186 while needed > 64: 

1187 new_labels.append(_MAXIMAL_OCTET * 63) 

1188 needed -= 64 

1189 if needed >= 2: 

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

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

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

1193 # label requires at least 2 bytes. 

1194 new_labels = list(reversed(new_labels)) 

1195 new_labels.extend(name.labels) 

1196 return Name(new_labels) 

1197 

1198 

1199def _pad_to_max_label(label, suffix_labels): 

1200 length = len(label) 

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

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

1203 if remaining <= 0: 

1204 # Shouldn't happen! 

1205 return label 

1206 needed = min(63 - length, remaining) 

1207 return label + _MAXIMAL_OCTET * needed 

1208 

1209 

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

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

1212 # 3.1.1. 

1213 # 

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

1215 # subdomain of origin. 

1216 if name == origin: 

1217 return _pad_to_max_name(name) 

1218 least_significant_label = name[0] 

1219 if least_significant_label == _MINIMAL_OCTET: 

1220 return name.parent() 

1221 least_octet = least_significant_label[-1] 

1222 suffix_labels = name.labels[1:] 

1223 if least_octet == _MINIMAL_OCTET_VALUE: 

1224 new_labels = [least_significant_label[:-1]] 

1225 else: 

1226 octets = bytearray(least_significant_label) 

1227 octet = octets[-1] 

1228 if octet == _LEFT_SQUARE_BRACKET_VALUE: 

1229 octet = _AT_SIGN_VALUE 

1230 else: 

1231 octet -= 1 

1232 octets[-1] = octet 

1233 least_significant_label = bytes(octets) 

1234 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)] 

1235 new_labels.extend(suffix_labels) 

1236 name = Name(new_labels) 

1237 if prefix_ok: 

1238 return _pad_to_max_name(name) 

1239 else: 

1240 return name 

1241 

1242 

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

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

1245 # 3.1.2. 

1246 # 

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

1248 # subdomain of origin. 

1249 if prefix_ok: 

1250 # Try prefixing \000 as new label 

1251 try: 

1252 return _SUCCESSOR_PREFIX.concatenate(name) 

1253 except NameTooLong: 

1254 pass 

1255 while name != origin: 

1256 # Try extending the least significant label. 

1257 least_significant_label = name[0] 

1258 if len(least_significant_label) < 63: 

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

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

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

1262 new_labels = [least_significant_label + _MINIMAL_OCTET] 

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

1264 try: 

1265 return Name(new_labels) 

1266 except NameTooLong: 

1267 pass 

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

1269 # signficant non-maximal byte in it. 

1270 octets = bytearray(least_significant_label) 

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

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

1273 # to the right of it. 

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

1275 octet = octets[i] 

1276 if octet == _MAXIMAL_OCTET_VALUE: 

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

1278 continue 

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

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

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

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

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

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

1285 if octet == _AT_SIGN_VALUE: 

1286 octet = _LEFT_SQUARE_BRACKET_VALUE 

1287 else: 

1288 octet += 1 

1289 octets[i] = octet 

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

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

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

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

1294 # always work. 

1295 return Name(new_labels) 

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

1297 # again. 

1298 name = name.parent() 

1299 

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

1301 # DNSSEC way. 

1302 return origin 

1303 

1304 

1305def _handle_relativity_and_call( 

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

1307 name: Name, 

1308 origin: Name, 

1309 prefix_ok: bool, 

1310) -> Name: 

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

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

1313 if not origin.is_absolute(): 

1314 raise NeedAbsoluteNameOrOrigin 

1315 relative = not name.is_absolute() 

1316 if relative: 

1317 name = name.derelativize(origin) 

1318 elif not name.is_subdomain(origin): 

1319 raise NeedSubdomainOfOrigin 

1320 result_name = function(name, origin, prefix_ok) 

1321 if relative: 

1322 result_name = result_name.relativize(origin) 

1323 return result_name