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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

614 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 encodings.idna # pyright: ignore 

22import functools 

23import struct 

24from collections.abc import Callable, Iterable 

25from typing import Any 

26 

27import dns._features 

28import dns.enum 

29import dns.exception 

30import dns.immutable 

31import dns.wirebase 

32 

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 

37 

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

39 import idna # pyright: ignore 

40 

41 have_idna_2008 = True 

42else: # pragma: no cover 

43 have_idna_2008 = False 

44 

45 

46CompressType = dict["Name", int] 

47 

48 

49class NameRelation(dns.enum.IntEnum): 

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

51 

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

53 # has hardwired the constants. 

54 

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 

65 

66 @classmethod 

67 def _maximum(cls): 

68 return cls.COMMONANCESTOR # pragma: no cover 

69 

70 @classmethod 

71 def _short_name(cls): 

72 return cls.__name__ # pragma: no cover 

73 

74 

75# Backwards compatibility 

76NAMERELN_NONE = NameRelation.NONE 

77NAMERELN_SUPERDOMAIN = NameRelation.SUPERDOMAIN 

78NAMERELN_SUBDOMAIN = NameRelation.SUBDOMAIN 

79NAMERELN_EQUAL = NameRelation.EQUAL 

80NAMERELN_COMMONANCESTOR = NameRelation.COMMONANCESTOR 

81 

82 

83class EmptyLabel(dns.exception.SyntaxError): 

84 """A DNS label is empty.""" 

85 

86 

87class BadEscape(dns.exception.SyntaxError): 

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

89 

90 

91class BadPointer(dns.exception.FormError): 

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

93 

94 

95class BadLabelType(dns.exception.FormError): 

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

97 

98 

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.""" 

102 

103 

104class NameTooLong(dns.exception.FormError): 

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

106 

107 

108class LabelTooLong(dns.exception.SyntaxError): 

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

110 

111 

112class AbsoluteConcatenation(dns.exception.DNSException): 

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

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

115 

116 

117class NoParent(dns.exception.DNSException): 

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

119 or the empty name.""" 

120 

121 

122class NoIDNA2008(dns.exception.DNSException): 

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

124 available.""" 

125 

126 

127class IDNAException(dns.exception.DNSException): 

128 """IDNA processing raised an exception.""" 

129 

130 supp_kwargs = {"idna_exception"} 

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

132 

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) 

137 

138 

139class NeedSubdomainOfOrigin(dns.exception.DNSException): 

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

141 

142 

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

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

145 

146 

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

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

149 @returns: the escaped string 

150 @rtype: string""" 

151 if isinstance(label, bytes): 

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

153 # < 0x20 or > 0x7f. 

154 text = "" 

155 for c in label: 

156 if c in _escaped: 

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

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

159 text += chr(c) 

160 else: 

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

162 return text 

163 

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 

174 

175 

176class IDNACodec: 

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

178 

179 def __init__(self): 

180 pass 

181 

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

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

184 

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

186 raise NotImplementedError # pragma: no cover 

187 

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) 

198 

199 

200class IDNA2003Codec(IDNACodec): 

201 """IDNA 2003 encoder/decoder.""" 

202 

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

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

205 

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 """ 

210 

211 super().__init__() 

212 self.strict_decode = strict_decode 

213 

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

215 """Encode *label*.""" 

216 

217 if label == "": 

218 return b"" 

219 try: 

220 return encodings.idna.ToASCII(label) 

221 except UnicodeError: 

222 raise LabelTooLong 

223 

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) 

234 

235 

236class IDNA2008Codec(IDNACodec): 

237 """IDNA 2008 encoder/decoder.""" 

238 

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. 

247 

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. 

252 

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

254 "transitional" mode described in Unicode Technical Standard 

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

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

257 

258 *allow_pure_ascii* is a ``bool``. If True, then a label which 

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

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

261 names, e.g. a name with starting with "_sip._tcp." and ending 

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

263 default is False. 

264 

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

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

267 was encoded with IDNA2003. The default is False. 

268 """ 

269 super().__init__() 

270 self.uts_46 = uts_46 

271 self.transitional = transitional 

272 self.allow_pure_ascii = allow_pure_ascii 

273 self.strict_decode = strict_decode 

274 

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

276 if label == "": 

277 return b"" 

278 if self.allow_pure_ascii and is_all_ascii(label): 

279 encoded = label.encode("ascii") 

280 if len(encoded) > 63: 

281 raise LabelTooLong 

282 return encoded 

283 if not have_idna_2008: 

284 raise NoIDNA2008 

285 try: 

286 if self.uts_46: 

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

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

289 return idna.alabel(label) 

290 except idna.IDNAError as e: 

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

292 raise LabelTooLong 

293 else: 

294 raise IDNAException(idna_exception=e) 

295 

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

297 if not self.strict_decode: 

298 return super().decode(label) 

299 if label == b"": 

300 return "" 

301 if not have_idna_2008: 

302 raise NoIDNA2008 

303 try: 

304 ulabel = idna.ulabel(label) 

305 if self.uts_46: 

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

307 return _escapify(ulabel) 

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

309 raise IDNAException(idna_exception=e) 

310 

311 

312IDNA_2003_Practical = IDNA2003Codec(False) 

313IDNA_2003_Strict = IDNA2003Codec(True) 

314IDNA_2003 = IDNA_2003_Practical 

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

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

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

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

319IDNA_2008 = IDNA_2008_Practical 

320if have_idna_2008: 

321 IDNA_DEFAULT = IDNA_2008_Practical 

322else: 

323 IDNA_DEFAULT = IDNA_2003_Practical 

324 

325 

326def set_default_idna_codec(idna_codec: IDNACodec): 

327 """Set the default IDNA codec.""" 

328 global IDNA_DEFAULT 

329 IDNA_DEFAULT = idna_codec 

330 

331 

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

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

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

335 

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

337 

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 

344 l = len(labels) 

345 total = 0 

346 i = -1 

347 j = 0 

348 for label in labels: 

349 ll = len(label) 

350 total += ll + 1 

351 if ll > 63: 

352 raise LabelTooLong 

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

354 i = j 

355 j += 1 

356 if total > 255: 

357 raise NameTooLong 

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

359 raise EmptyLabel 

360 

361 

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

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

364 ``bytes`` just return it. 

365 

366 """ 

367 

368 if isinstance(label, bytes): 

369 return label 

370 else: 

371 return label.encode() 

372 

373 

374@dns.immutable.immutable 

375class Name: 

376 """A DNS name. 

377 

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

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

380 of the class are immutable. 

381 """ 

382 

383 __slots__ = ["labels"] 

384 

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

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

387 

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

389 self.labels = tuple(blabels) 

390 _validate_labels(self.labels) 

391 

392 def __copy__(self): 

393 return Name(self.labels) 

394 

395 def __deepcopy__(self, memo): 

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

397 

398 def __getstate__(self): 

399 # Names can be pickled 

400 return {"labels": self.labels} 

401 

402 def __setstate__(self, state): 

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

404 _validate_labels(self.labels) 

405 

406 def is_absolute(self) -> bool: 

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

408 

409 Returns a ``bool``. 

410 """ 

411 

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

413 

414 def is_wild(self) -> bool: 

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

416 

417 Returns a ``bool``. 

418 """ 

419 

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

421 

422 def __hash__(self) -> int: 

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

424 

425 Returns an ``int``. 

426 """ 

427 

428 h = 0 

429 for label in self.labels: 

430 for c in label.lower(): 

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

432 return h 

433 

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

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

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

437 

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

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

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

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

442 

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

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

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

446 the DNSSEC order relation is used to order them. 

447 

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

449 have in common. 

450 

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

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

453 

454 ============= ============= =========== ===== ======= 

455 self other relation order nlabels 

456 ============= ============= =========== ===== ======= 

457 www.example. www.example. equal 0 3 

458 www.example. example. subdomain > 0 2 

459 example. www.example. superdomain < 0 2 

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

461 example1 example2. none < 0 0 

462 example1. example2 none > 0 0 

463 ============= ============= =========== ===== ======= 

464 """ 

465 

466 sabs = self.is_absolute() 

467 oabs = other.is_absolute() 

468 if sabs != oabs: 

469 if sabs: 

470 return (NameRelation.NONE, 1, 0) 

471 else: 

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

473 l1 = len(self.labels) 

474 l2 = len(other.labels) 

475 ldiff = l1 - l2 

476 if ldiff < 0: 

477 l = l1 

478 else: 

479 l = l2 

480 

481 order = 0 

482 nlabels = 0 

483 namereln = NameRelation.NONE 

484 while l > 0: 

485 l -= 1 

486 l1 -= 1 

487 l2 -= 1 

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

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

490 if label1 < label2: 

491 order = -1 

492 if nlabels > 0: 

493 namereln = NameRelation.COMMONANCESTOR 

494 return (namereln, order, nlabels) 

495 elif label1 > label2: 

496 order = 1 

497 if nlabels > 0: 

498 namereln = NameRelation.COMMONANCESTOR 

499 return (namereln, order, nlabels) 

500 nlabels += 1 

501 order = ldiff 

502 if ldiff < 0: 

503 namereln = NameRelation.SUPERDOMAIN 

504 elif ldiff > 0: 

505 namereln = NameRelation.SUBDOMAIN 

506 else: 

507 namereln = NameRelation.EQUAL 

508 return (namereln, order, nlabels) 

509 

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

511 """Is self a subdomain of other? 

512 

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

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

515 

516 Returns a ``bool``. 

517 """ 

518 

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

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

521 return True 

522 return False 

523 

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

525 """Is self a superdomain of other? 

526 

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

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

529 

530 Returns a ``bool``. 

531 """ 

532 

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

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

535 return True 

536 return False 

537 

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

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

540 DNSSEC canonical form. 

541 """ 

542 

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

544 

545 def __eq__(self, other): 

546 if isinstance(other, Name): 

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

548 else: 

549 return False 

550 

551 def __ne__(self, other): 

552 if isinstance(other, Name): 

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

554 else: 

555 return True 

556 

557 def __lt__(self, other): 

558 if isinstance(other, Name): 

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

560 else: 

561 return NotImplemented 

562 

563 def __le__(self, other): 

564 if isinstance(other, Name): 

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

566 else: 

567 return NotImplemented 

568 

569 def __ge__(self, other): 

570 if isinstance(other, Name): 

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

572 else: 

573 return NotImplemented 

574 

575 def __gt__(self, other): 

576 if isinstance(other, Name): 

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

578 else: 

579 return NotImplemented 

580 

581 def __repr__(self): 

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

583 

584 def __str__(self): 

585 return self.to_text(False) 

586 

587 def to_text(self, omit_final_dot: bool = False) -> str: 

588 """Convert name to DNS text format. 

589 

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

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

592 is False. 

593 

594 Returns a ``str``. 

595 """ 

596 

597 if len(self.labels) == 0: 

598 return "@" 

599 if len(self.labels) == 1 and self.labels[0] == b"": 

600 return "." 

601 if omit_final_dot and self.is_absolute(): 

602 l = self.labels[:-1] 

603 else: 

604 l = self.labels 

605 s = ".".join(map(_escapify, l)) 

606 return s 

607 

608 def to_unicode( 

609 self, omit_final_dot: bool = False, idna_codec: IDNACodec | None = None 

610 ) -> str: 

611 """Convert name to Unicode text format. 

612 

613 IDN ACE labels are converted to Unicode. 

614 

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

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

617 is False. 

618 *idna_codec* specifies the IDNA encoder/decoder. If None, the 

619 dns.name.IDNA_DEFAULT encoder/decoder is used. 

620 

621 Returns a ``str``. 

622 """ 

623 

624 if len(self.labels) == 0: 

625 return "@" 

626 if len(self.labels) == 1 and self.labels[0] == b"": 

627 return "." 

628 if omit_final_dot and self.is_absolute(): 

629 l = self.labels[:-1] 

630 else: 

631 l = self.labels 

632 if idna_codec is None: 

633 idna_codec = IDNA_DEFAULT 

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

635 

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

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

638 

639 The name is canonicalized and converted to uncompressed wire 

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

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

642 

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

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

645 to the name. 

646 

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

648 relative and no origin was provided. 

649 

650 Returns a ``bytes``. 

651 """ 

652 

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

654 assert digest is not None 

655 return digest 

656 

657 def to_wire( 

658 self, 

659 file: Any | None = None, 

660 compress: CompressType | None = None, 

661 origin: "Name | None" = None, 

662 canonicalize: bool = False, 

663 ) -> bytes | None: 

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

665 

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

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

668 containing the wire name will be returned. 

669 

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

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

672 the compression code assumes that compression offset 0 is the 

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

674 if this is not the case. 

675 

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

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

678 to it. 

679 

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

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

682 digesting in hashes. 

683 

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

685 relative and no origin was provided. 

686 

687 Returns a ``bytes`` or ``None``. 

688 """ 

689 

690 if file is None: 

691 out = bytearray() 

692 for label in self.labels: 

693 out.append(len(label)) 

694 if canonicalize: 

695 out += label.lower() 

696 else: 

697 out += label 

698 if not self.is_absolute(): 

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

700 raise NeedAbsoluteNameOrOrigin 

701 for label in origin.labels: 

702 out.append(len(label)) 

703 if canonicalize: 

704 out += label.lower() 

705 else: 

706 out += label 

707 return bytes(out) 

708 

709 labels: Iterable[bytes] 

710 if not self.is_absolute(): 

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

712 raise NeedAbsoluteNameOrOrigin 

713 labels = list(self.labels) 

714 labels.extend(list(origin.labels)) 

715 else: 

716 labels = self.labels 

717 i = 0 

718 for label in labels: 

719 n = Name(labels[i:]) 

720 i += 1 

721 if compress is not None: 

722 pos = compress.get(n) 

723 else: 

724 pos = None 

725 if pos is not None: 

726 value = 0xC000 + pos 

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

728 file.write(s) 

729 break 

730 else: 

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

732 pos = file.tell() 

733 if pos <= 0x3FFF: 

734 compress[n] = pos 

735 l = len(label) 

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

737 if l > 0: 

738 if canonicalize: 

739 file.write(label.lower()) 

740 else: 

741 file.write(label) 

742 return None 

743 

744 def __len__(self) -> int: 

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

746 

747 Returns an ``int``. 

748 """ 

749 

750 return len(self.labels) 

751 

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

753 return self.labels[index] 

754 

755 def __add__(self, other): 

756 return self.concatenate(other) 

757 

758 def __sub__(self, other): 

759 return self.relativize(other) 

760 

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

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

763 

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

765 

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

767 name. 

768 

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

770 """ 

771 

772 l = len(self.labels) 

773 if depth == 0: 

774 return (self, empty) 

775 elif depth == l: 

776 return (empty, self) 

777 elif depth < 0 or depth > l: 

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

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

780 

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

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

783 

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

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

786 

787 Returns a ``dns.name.Name``. 

788 """ 

789 

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

791 raise AbsoluteConcatenation 

792 labels = list(self.labels) 

793 labels.extend(list(other.labels)) 

794 return Name(labels) 

795 

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

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

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

799 

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

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

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

803 

804 Returns a ``dns.name.Name``. 

805 """ 

806 

807 if self.is_subdomain(origin): 

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

809 else: 

810 return self 

811 

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

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

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

815 

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

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

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

819 

820 Returns a ``dns.name.Name``. 

821 """ 

822 

823 if not self.is_absolute(): 

824 return self.concatenate(origin) 

825 else: 

826 return self 

827 

828 def choose_relativity( 

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

830 ) -> "Name": 

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

832 

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

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

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

836 derelativized. 

837 

838 Returns a ``dns.name.Name``. 

839 """ 

840 

841 if origin: 

842 if relativize: 

843 return self.relativize(origin) 

844 else: 

845 return self.derelativize(origin) 

846 else: 

847 return self 

848 

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

850 """Return the parent of the name. 

851 

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

853 

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

855 empty name, and thus has no parent. 

856 

857 Returns a ``dns.name.Name``. 

858 """ 

859 

860 if self == root or self == empty: 

861 raise NoParent 

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

863 

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

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

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

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

868 *origin* due to length considerations. 

869 

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

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

872 is absolute then the predecessor will be absolute. 

873 

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

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

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

877 """ 

878 return _handle_relativity_and_call( 

879 _absolute_predecessor, self, origin, prefix_ok 

880 ) 

881 

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

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

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

885 computed due to name length limitations. 

886 

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

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

889 

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

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

892 is absolute then the successor will be absolute. 

893 

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

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

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

897 """ 

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

899 

900 

901#: The root name, '.' 

902root = Name([b""]) 

903 

904#: The empty name. 

905empty = Name([]) 

906 

907 

908def from_unicode( 

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

910) -> Name: 

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

912 

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

914 the IDNA codec. 

915 

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

917 

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

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

920 

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

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

923 is used. 

924 

925 Returns a ``dns.name.Name``. 

926 """ 

927 

928 labels = [] 

929 label = "" 

930 escaping = False 

931 edigits = 0 

932 total = 0 

933 if idna_codec is None: 

934 idna_codec = IDNA_DEFAULT 

935 if text == "@": 

936 text = "" 

937 if text: 

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

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

940 for c in text: 

941 if escaping: 

942 if edigits == 0: 

943 if c.isdigit(): 

944 total = int(c) 

945 edigits += 1 

946 else: 

947 label += c 

948 escaping = False 

949 else: 

950 if not c.isdigit(): 

951 raise BadEscape 

952 total *= 10 

953 total += int(c) 

954 edigits += 1 

955 if edigits == 3: 

956 escaping = False 

957 label += chr(total) 

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

959 if len(label) == 0: 

960 raise EmptyLabel 

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

962 label = "" 

963 elif c == "\\": 

964 escaping = True 

965 edigits = 0 

966 total = 0 

967 else: 

968 label += c 

969 if escaping: 

970 raise BadEscape 

971 if len(label) > 0: 

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

973 else: 

974 labels.append(b"") 

975 

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

977 labels.extend(list(origin.labels)) 

978 return Name(labels) 

979 

980 

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

982 for c in text: 

983 if ord(c) > 0x7F: 

984 return False 

985 return True 

986 

987 

988def from_text( 

989 text: bytes | str, 

990 origin: Name | None = root, 

991 idna_codec: IDNACodec | None = None, 

992) -> Name: 

993 """Convert text into a Name object. 

994 

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

996 

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

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

999 

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

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

1002 is used. 

1003 

1004 Returns a ``dns.name.Name``. 

1005 """ 

1006 

1007 if isinstance(text, str): 

1008 if not is_all_ascii(text): 

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

1010 return from_unicode(text, origin, idna_codec) 

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

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

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

1014 # 

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

1016 # 

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

1018 # codepoints > 127. 

1019 text = text.encode("ascii") 

1020 labels = [] 

1021 label = b"" 

1022 escaping = False 

1023 edigits = 0 

1024 total = 0 

1025 if text == b"@": 

1026 text = b"" 

1027 if text: 

1028 if text == b".": 

1029 return Name([b""]) 

1030 for c in text: 

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

1032 if escaping: 

1033 if edigits == 0: 

1034 if byte_.isdigit(): 

1035 total = int(byte_) 

1036 edigits += 1 

1037 else: 

1038 label += byte_ 

1039 escaping = False 

1040 else: 

1041 if not byte_.isdigit(): 

1042 raise BadEscape 

1043 total *= 10 

1044 total += int(byte_) 

1045 edigits += 1 

1046 if edigits == 3: 

1047 escaping = False 

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

1049 elif byte_ == b".": 

1050 if len(label) == 0: 

1051 raise EmptyLabel 

1052 labels.append(label) 

1053 label = b"" 

1054 elif byte_ == b"\\": 

1055 escaping = True 

1056 edigits = 0 

1057 total = 0 

1058 else: 

1059 label += byte_ 

1060 if escaping: 

1061 raise BadEscape 

1062 if len(label) > 0: 

1063 labels.append(label) 

1064 else: 

1065 labels.append(b"") 

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

1067 labels.extend(list(origin.labels)) 

1068 return Name(labels) 

1069 

1070 

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

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

1073 

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

1075 

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

1077 point backwards in the message. 

1078 

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

1080 

1081 Returns a ``dns.name.Name`` 

1082 """ 

1083 

1084 labels = [] 

1085 biggest_pointer = parser.current 

1086 with parser.restore_furthest(): 

1087 count = parser.get_uint8() 

1088 while count != 0: 

1089 if count < 64: 

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

1091 elif count >= 192: 

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

1093 if current >= biggest_pointer: 

1094 raise BadPointer 

1095 biggest_pointer = current 

1096 parser.seek(current) 

1097 else: 

1098 raise BadLabelType 

1099 count = parser.get_uint8() 

1100 labels.append(b"") 

1101 return Name(labels) 

1102 

1103 

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

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

1106 

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

1108 wire form. 

1109 

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

1111 from the start of the message 

1112 

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

1114 point backwards in the message. 

1115 

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

1117 

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

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

1120 which were consumed reading it. 

1121 """ 

1122 

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

1124 name = from_wire_parser(parser) 

1125 return (name, parser.current - current) 

1126 

1127 

1128# RFC 4471 Support 

1129 

1130_MINIMAL_OCTET = b"\x00" 

1131_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET) 

1132_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET]) 

1133_MAXIMAL_OCTET = b"\xff" 

1134_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET) 

1135_AT_SIGN_VALUE = ord("@") 

1136_LEFT_SQUARE_BRACKET_VALUE = ord("[") 

1137 

1138 

1139def _wire_length(labels): 

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

1141 

1142 

1143def _pad_to_max_name(name): 

1144 needed = 255 - _wire_length(name.labels) 

1145 new_labels = [] 

1146 while needed > 64: 

1147 new_labels.append(_MAXIMAL_OCTET * 63) 

1148 needed -= 64 

1149 if needed >= 2: 

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

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

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

1153 # label requires at least 2 bytes. 

1154 new_labels = list(reversed(new_labels)) 

1155 new_labels.extend(name.labels) 

1156 return Name(new_labels) 

1157 

1158 

1159def _pad_to_max_label(label, suffix_labels): 

1160 length = len(label) 

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

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

1163 if remaining <= 0: 

1164 # Shouldn't happen! 

1165 return label 

1166 needed = min(63 - length, remaining) 

1167 return label + _MAXIMAL_OCTET * needed 

1168 

1169 

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

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

1172 # 3.1.1. 

1173 # 

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

1175 # subdomain of origin. 

1176 if name == origin: 

1177 return _pad_to_max_name(name) 

1178 least_significant_label = name[0] 

1179 if least_significant_label == _MINIMAL_OCTET: 

1180 return name.parent() 

1181 least_octet = least_significant_label[-1] 

1182 suffix_labels = name.labels[1:] 

1183 if least_octet == _MINIMAL_OCTET_VALUE: 

1184 new_labels = [least_significant_label[:-1]] 

1185 else: 

1186 octets = bytearray(least_significant_label) 

1187 octet = octets[-1] 

1188 if octet == _LEFT_SQUARE_BRACKET_VALUE: 

1189 octet = _AT_SIGN_VALUE 

1190 else: 

1191 octet -= 1 

1192 octets[-1] = octet 

1193 least_significant_label = bytes(octets) 

1194 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)] 

1195 new_labels.extend(suffix_labels) 

1196 name = Name(new_labels) 

1197 if prefix_ok: 

1198 return _pad_to_max_name(name) 

1199 else: 

1200 return name 

1201 

1202 

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

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

1205 # 3.1.2. 

1206 # 

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

1208 # subdomain of origin. 

1209 if prefix_ok: 

1210 # Try prefixing \000 as new label 

1211 try: 

1212 return _SUCCESSOR_PREFIX.concatenate(name) 

1213 except NameTooLong: 

1214 pass 

1215 while name != origin: 

1216 # Try extending the least significant label. 

1217 least_significant_label = name[0] 

1218 if len(least_significant_label) < 63: 

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

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

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

1222 new_labels = [least_significant_label + _MINIMAL_OCTET] 

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

1224 try: 

1225 return Name(new_labels) 

1226 except NameTooLong: 

1227 pass 

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

1229 # signficant non-maximal byte in it. 

1230 octets = bytearray(least_significant_label) 

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

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

1233 # to the right of it. 

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

1235 octet = octets[i] 

1236 if octet == _MAXIMAL_OCTET_VALUE: 

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

1238 continue 

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

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

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

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

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

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

1245 if octet == _AT_SIGN_VALUE: 

1246 octet = _LEFT_SQUARE_BRACKET_VALUE 

1247 else: 

1248 octet += 1 

1249 octets[i] = octet 

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

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

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

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

1254 # always work. 

1255 return Name(new_labels) 

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

1257 # again. 

1258 name = name.parent() 

1259 

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

1261 # DNSSEC way. 

1262 return origin 

1263 

1264 

1265def _handle_relativity_and_call( 

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

1267 name: Name, 

1268 origin: Name, 

1269 prefix_ok: bool, 

1270) -> Name: 

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

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

1273 if not origin.is_absolute(): 

1274 raise NeedAbsoluteNameOrOrigin 

1275 relative = not name.is_absolute() 

1276 if relative: 

1277 name = name.derelativize(origin) 

1278 elif not name.is_subdomain(origin): 

1279 raise NeedSubdomainOfOrigin 

1280 result_name = function(name, origin, prefix_ok) 

1281 if relative: 

1282 result_name = result_name.relativize(origin) 

1283 return result_name