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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

609 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 # type: 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 # type: 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. 

256 

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

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

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

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

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

262 default is False. 

263 

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

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

266 was encoded with IDNA2003. The default is False. 

267 """ 

268 super().__init__() 

269 self.uts_46 = uts_46 

270 self.transitional = transitional 

271 self.allow_pure_ascii = allow_pure_ascii 

272 self.strict_decode = strict_decode 

273 

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

275 if label == "": 

276 return b"" 

277 if self.allow_pure_ascii and is_all_ascii(label): 

278 encoded = label.encode("ascii") 

279 if len(encoded) > 63: 

280 raise LabelTooLong 

281 return encoded 

282 if not have_idna_2008: 

283 raise NoIDNA2008 

284 try: 

285 if self.uts_46: 

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

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

288 return idna.alabel(label) 

289 except idna.IDNAError as e: 

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

291 raise LabelTooLong 

292 else: 

293 raise IDNAException(idna_exception=e) 

294 

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

296 if not self.strict_decode: 

297 return super().decode(label) 

298 if label == b"": 

299 return "" 

300 if not have_idna_2008: 

301 raise NoIDNA2008 

302 try: 

303 ulabel = idna.ulabel(label) 

304 if self.uts_46: 

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

306 return _escapify(ulabel) 

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

308 raise IDNAException(idna_exception=e) 

309 

310 

311IDNA_2003_Practical = IDNA2003Codec(False) 

312IDNA_2003_Strict = IDNA2003Codec(True) 

313IDNA_2003 = IDNA_2003_Practical 

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

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

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

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

318IDNA_2008 = IDNA_2008_Practical 

319 

320 

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

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

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

324 

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

326 

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

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

329 sequence 

330 

331 """ 

332 

333 l = len(labels) 

334 total = 0 

335 i = -1 

336 j = 0 

337 for label in labels: 

338 ll = len(label) 

339 total += ll + 1 

340 if ll > 63: 

341 raise LabelTooLong 

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

343 i = j 

344 j += 1 

345 if total > 255: 

346 raise NameTooLong 

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

348 raise EmptyLabel 

349 

350 

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

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

353 ``bytes`` just return it. 

354 

355 """ 

356 

357 if isinstance(label, bytes): 

358 return label 

359 else: 

360 return label.encode() 

361 

362 

363@dns.immutable.immutable 

364class Name: 

365 """A DNS name. 

366 

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

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

369 of the class are immutable. 

370 """ 

371 

372 __slots__ = ["labels"] 

373 

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

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

376 

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

378 self.labels = tuple(blabels) 

379 _validate_labels(self.labels) 

380 

381 def __copy__(self): 

382 return Name(self.labels) 

383 

384 def __deepcopy__(self, memo): 

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

386 

387 def __getstate__(self): 

388 # Names can be pickled 

389 return {"labels": self.labels} 

390 

391 def __setstate__(self, state): 

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

393 _validate_labels(self.labels) 

394 

395 def is_absolute(self) -> bool: 

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

397 

398 Returns a ``bool``. 

399 """ 

400 

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

402 

403 def is_wild(self) -> bool: 

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

405 

406 Returns a ``bool``. 

407 """ 

408 

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

410 

411 def __hash__(self) -> int: 

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

413 

414 Returns an ``int``. 

415 """ 

416 

417 h = 0 

418 for label in self.labels: 

419 for c in label.lower(): 

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

421 return h 

422 

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

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

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

426 

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

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

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

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

431 

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

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

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

435 the DNSSEC order relation is used to order them. 

436 

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

438 have in common. 

439 

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

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

442 

443 ============= ============= =========== ===== ======= 

444 self other relation order nlabels 

445 ============= ============= =========== ===== ======= 

446 www.example. www.example. equal 0 3 

447 www.example. example. subdomain > 0 2 

448 example. www.example. superdomain < 0 2 

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

450 example1 example2. none < 0 0 

451 example1. example2 none > 0 0 

452 ============= ============= =========== ===== ======= 

453 """ 

454 

455 sabs = self.is_absolute() 

456 oabs = other.is_absolute() 

457 if sabs != oabs: 

458 if sabs: 

459 return (NameRelation.NONE, 1, 0) 

460 else: 

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

462 l1 = len(self.labels) 

463 l2 = len(other.labels) 

464 ldiff = l1 - l2 

465 if ldiff < 0: 

466 l = l1 

467 else: 

468 l = l2 

469 

470 order = 0 

471 nlabels = 0 

472 namereln = NameRelation.NONE 

473 while l > 0: 

474 l -= 1 

475 l1 -= 1 

476 l2 -= 1 

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

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

479 if label1 < label2: 

480 order = -1 

481 if nlabels > 0: 

482 namereln = NameRelation.COMMONANCESTOR 

483 return (namereln, order, nlabels) 

484 elif label1 > label2: 

485 order = 1 

486 if nlabels > 0: 

487 namereln = NameRelation.COMMONANCESTOR 

488 return (namereln, order, nlabels) 

489 nlabels += 1 

490 order = ldiff 

491 if ldiff < 0: 

492 namereln = NameRelation.SUPERDOMAIN 

493 elif ldiff > 0: 

494 namereln = NameRelation.SUBDOMAIN 

495 else: 

496 namereln = NameRelation.EQUAL 

497 return (namereln, order, nlabels) 

498 

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

500 """Is self a subdomain of other? 

501 

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

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

504 

505 Returns a ``bool``. 

506 """ 

507 

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

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

510 return True 

511 return False 

512 

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

514 """Is self a superdomain of other? 

515 

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

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

518 

519 Returns a ``bool``. 

520 """ 

521 

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

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

524 return True 

525 return False 

526 

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

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

529 DNSSEC canonical form. 

530 """ 

531 

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

533 

534 def __eq__(self, other): 

535 if isinstance(other, Name): 

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

537 else: 

538 return False 

539 

540 def __ne__(self, other): 

541 if isinstance(other, Name): 

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

543 else: 

544 return True 

545 

546 def __lt__(self, other): 

547 if isinstance(other, Name): 

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

549 else: 

550 return NotImplemented 

551 

552 def __le__(self, other): 

553 if isinstance(other, Name): 

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

555 else: 

556 return NotImplemented 

557 

558 def __ge__(self, other): 

559 if isinstance(other, Name): 

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

561 else: 

562 return NotImplemented 

563 

564 def __gt__(self, other): 

565 if isinstance(other, Name): 

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

567 else: 

568 return NotImplemented 

569 

570 def __repr__(self): 

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

572 

573 def __str__(self): 

574 return self.to_text(False) 

575 

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

577 """Convert name to DNS text format. 

578 

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

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

581 is False. 

582 

583 Returns a ``str``. 

584 """ 

585 

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

587 return "@" 

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

589 return "." 

590 if omit_final_dot and self.is_absolute(): 

591 l = self.labels[:-1] 

592 else: 

593 l = self.labels 

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

595 return s 

596 

597 def to_unicode( 

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

599 ) -> str: 

600 """Convert name to Unicode text format. 

601 

602 IDN ACE labels are converted to Unicode. 

603 

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

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

606 is False. 

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

608 dns.name.IDNA_2003_Practical encoder/decoder is used. 

609 The IDNA_2003_Practical decoder does 

610 not impose any policy, it just decodes punycode, so if you 

611 don't want checking for compliance, you can use this decoder 

612 for IDNA2008 as well. 

613 

614 Returns a ``str``. 

615 """ 

616 

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

618 return "@" 

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

620 return "." 

621 if omit_final_dot and self.is_absolute(): 

622 l = self.labels[:-1] 

623 else: 

624 l = self.labels 

625 if idna_codec is None: 

626 idna_codec = IDNA_2003_Practical 

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

628 

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

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

631 

632 The name is canonicalized and converted to uncompressed wire 

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

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

635 

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

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

638 to the name. 

639 

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

641 relative and no origin was provided. 

642 

643 Returns a ``bytes``. 

644 """ 

645 

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

647 assert digest is not None 

648 return digest 

649 

650 def to_wire( 

651 self, 

652 file: Any | None = None, 

653 compress: CompressType | None = None, 

654 origin: "Name | None" = None, 

655 canonicalize: bool = False, 

656 ) -> bytes | None: 

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

658 

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

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

661 containing the wire name will be returned. 

662 

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

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

665 the compression code assumes that compression offset 0 is the 

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

667 if this is not the case. 

668 

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

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

671 to it. 

672 

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

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

675 digesting in hashes. 

676 

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

678 relative and no origin was provided. 

679 

680 Returns a ``bytes`` or ``None``. 

681 """ 

682 

683 if file is None: 

684 out = bytearray() 

685 for label in self.labels: 

686 out.append(len(label)) 

687 if canonicalize: 

688 out += label.lower() 

689 else: 

690 out += label 

691 if not self.is_absolute(): 

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

693 raise NeedAbsoluteNameOrOrigin 

694 for label in origin.labels: 

695 out.append(len(label)) 

696 if canonicalize: 

697 out += label.lower() 

698 else: 

699 out += label 

700 return bytes(out) 

701 

702 labels: Iterable[bytes] 

703 if not self.is_absolute(): 

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

705 raise NeedAbsoluteNameOrOrigin 

706 labels = list(self.labels) 

707 labels.extend(list(origin.labels)) 

708 else: 

709 labels = self.labels 

710 i = 0 

711 for label in labels: 

712 n = Name(labels[i:]) 

713 i += 1 

714 if compress is not None: 

715 pos = compress.get(n) 

716 else: 

717 pos = None 

718 if pos is not None: 

719 value = 0xC000 + pos 

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

721 file.write(s) 

722 break 

723 else: 

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

725 pos = file.tell() 

726 if pos <= 0x3FFF: 

727 compress[n] = pos 

728 l = len(label) 

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

730 if l > 0: 

731 if canonicalize: 

732 file.write(label.lower()) 

733 else: 

734 file.write(label) 

735 return None 

736 

737 def __len__(self) -> int: 

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

739 

740 Returns an ``int``. 

741 """ 

742 

743 return len(self.labels) 

744 

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

746 return self.labels[index] 

747 

748 def __add__(self, other): 

749 return self.concatenate(other) 

750 

751 def __sub__(self, other): 

752 return self.relativize(other) 

753 

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

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

756 

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

758 

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

760 name. 

761 

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

763 """ 

764 

765 l = len(self.labels) 

766 if depth == 0: 

767 return (self, empty) 

768 elif depth == l: 

769 return (empty, self) 

770 elif depth < 0 or depth > l: 

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

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

773 

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

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

776 

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

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

779 

780 Returns a ``dns.name.Name``. 

781 """ 

782 

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

784 raise AbsoluteConcatenation 

785 labels = list(self.labels) 

786 labels.extend(list(other.labels)) 

787 return Name(labels) 

788 

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

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

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

792 

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

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

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

796 

797 Returns a ``dns.name.Name``. 

798 """ 

799 

800 if self.is_subdomain(origin): 

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

802 else: 

803 return self 

804 

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

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

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

808 

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

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

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

812 

813 Returns a ``dns.name.Name``. 

814 """ 

815 

816 if not self.is_absolute(): 

817 return self.concatenate(origin) 

818 else: 

819 return self 

820 

821 def choose_relativity( 

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

823 ) -> "Name": 

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

825 

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

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

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

829 derelativized. 

830 

831 Returns a ``dns.name.Name``. 

832 """ 

833 

834 if origin: 

835 if relativize: 

836 return self.relativize(origin) 

837 else: 

838 return self.derelativize(origin) 

839 else: 

840 return self 

841 

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

843 """Return the parent of the name. 

844 

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

846 

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

848 empty name, and thus has no parent. 

849 

850 Returns a ``dns.name.Name``. 

851 """ 

852 

853 if self == root or self == empty: 

854 raise NoParent 

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

856 

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

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

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

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

861 *origin* due to length considerations. 

862 

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

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

865 is absolute then the predecessor will be absolute. 

866 

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

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

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

870 """ 

871 return _handle_relativity_and_call( 

872 _absolute_predecessor, self, origin, prefix_ok 

873 ) 

874 

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

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

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

878 computed due to name length limitations. 

879 

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

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

882 

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

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

885 is absolute then the successor will be absolute. 

886 

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

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

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

890 """ 

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

892 

893 

894#: The root name, '.' 

895root = Name([b""]) 

896 

897#: The empty name. 

898empty = Name([]) 

899 

900 

901def from_unicode( 

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

903) -> Name: 

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

905 

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

907 the IDNA codec. 

908 

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

910 

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

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

913 

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

915 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder 

916 is used. 

917 

918 Returns a ``dns.name.Name``. 

919 """ 

920 

921 labels = [] 

922 label = "" 

923 escaping = False 

924 edigits = 0 

925 total = 0 

926 if idna_codec is None: 

927 idna_codec = IDNA_2003 

928 if text == "@": 

929 text = "" 

930 if text: 

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

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

933 for c in text: 

934 if escaping: 

935 if edigits == 0: 

936 if c.isdigit(): 

937 total = int(c) 

938 edigits += 1 

939 else: 

940 label += c 

941 escaping = False 

942 else: 

943 if not c.isdigit(): 

944 raise BadEscape 

945 total *= 10 

946 total += int(c) 

947 edigits += 1 

948 if edigits == 3: 

949 escaping = False 

950 label += chr(total) 

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

952 if len(label) == 0: 

953 raise EmptyLabel 

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

955 label = "" 

956 elif c == "\\": 

957 escaping = True 

958 edigits = 0 

959 total = 0 

960 else: 

961 label += c 

962 if escaping: 

963 raise BadEscape 

964 if len(label) > 0: 

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

966 else: 

967 labels.append(b"") 

968 

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

970 labels.extend(list(origin.labels)) 

971 return Name(labels) 

972 

973 

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

975 for c in text: 

976 if ord(c) > 0x7F: 

977 return False 

978 return True 

979 

980 

981def from_text( 

982 text: bytes | str, 

983 origin: Name | None = root, 

984 idna_codec: IDNACodec | None = None, 

985) -> Name: 

986 """Convert text into a Name object. 

987 

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

989 

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

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

992 

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

994 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder 

995 is used. 

996 

997 Returns a ``dns.name.Name``. 

998 """ 

999 

1000 if isinstance(text, str): 

1001 if not is_all_ascii(text): 

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

1003 return from_unicode(text, origin, idna_codec) 

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

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

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

1007 # 

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

1009 # 

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

1011 # codepoints > 127. 

1012 text = text.encode("ascii") 

1013 labels = [] 

1014 label = b"" 

1015 escaping = False 

1016 edigits = 0 

1017 total = 0 

1018 if text == b"@": 

1019 text = b"" 

1020 if text: 

1021 if text == b".": 

1022 return Name([b""]) 

1023 for c in text: 

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

1025 if escaping: 

1026 if edigits == 0: 

1027 if byte_.isdigit(): 

1028 total = int(byte_) 

1029 edigits += 1 

1030 else: 

1031 label += byte_ 

1032 escaping = False 

1033 else: 

1034 if not byte_.isdigit(): 

1035 raise BadEscape 

1036 total *= 10 

1037 total += int(byte_) 

1038 edigits += 1 

1039 if edigits == 3: 

1040 escaping = False 

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

1042 elif byte_ == b".": 

1043 if len(label) == 0: 

1044 raise EmptyLabel 

1045 labels.append(label) 

1046 label = b"" 

1047 elif byte_ == b"\\": 

1048 escaping = True 

1049 edigits = 0 

1050 total = 0 

1051 else: 

1052 label += byte_ 

1053 if escaping: 

1054 raise BadEscape 

1055 if len(label) > 0: 

1056 labels.append(label) 

1057 else: 

1058 labels.append(b"") 

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

1060 labels.extend(list(origin.labels)) 

1061 return Name(labels) 

1062 

1063 

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

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

1066 

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

1068 

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

1070 point backwards in the message. 

1071 

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

1073 

1074 Returns a ``dns.name.Name`` 

1075 """ 

1076 

1077 labels = [] 

1078 biggest_pointer = parser.current 

1079 with parser.restore_furthest(): 

1080 count = parser.get_uint8() 

1081 while count != 0: 

1082 if count < 64: 

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

1084 elif count >= 192: 

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

1086 if current >= biggest_pointer: 

1087 raise BadPointer 

1088 biggest_pointer = current 

1089 parser.seek(current) 

1090 else: 

1091 raise BadLabelType 

1092 count = parser.get_uint8() 

1093 labels.append(b"") 

1094 return Name(labels) 

1095 

1096 

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

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

1099 

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

1101 wire form. 

1102 

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

1104 from the start of the message 

1105 

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

1107 point backwards in the message. 

1108 

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

1110 

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

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

1113 which were consumed reading it. 

1114 """ 

1115 

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

1117 name = from_wire_parser(parser) 

1118 return (name, parser.current - current) 

1119 

1120 

1121# RFC 4471 Support 

1122 

1123_MINIMAL_OCTET = b"\x00" 

1124_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET) 

1125_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET]) 

1126_MAXIMAL_OCTET = b"\xff" 

1127_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET) 

1128_AT_SIGN_VALUE = ord("@") 

1129_LEFT_SQUARE_BRACKET_VALUE = ord("[") 

1130 

1131 

1132def _wire_length(labels): 

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

1134 

1135 

1136def _pad_to_max_name(name): 

1137 needed = 255 - _wire_length(name.labels) 

1138 new_labels = [] 

1139 while needed > 64: 

1140 new_labels.append(_MAXIMAL_OCTET * 63) 

1141 needed -= 64 

1142 if needed >= 2: 

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

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

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

1146 # label requires at least 2 bytes. 

1147 new_labels = list(reversed(new_labels)) 

1148 new_labels.extend(name.labels) 

1149 return Name(new_labels) 

1150 

1151 

1152def _pad_to_max_label(label, suffix_labels): 

1153 length = len(label) 

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

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

1156 if remaining <= 0: 

1157 # Shouldn't happen! 

1158 return label 

1159 needed = min(63 - length, remaining) 

1160 return label + _MAXIMAL_OCTET * needed 

1161 

1162 

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

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

1165 # 3.1.1. 

1166 # 

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

1168 # subdomain of origin. 

1169 if name == origin: 

1170 return _pad_to_max_name(name) 

1171 least_significant_label = name[0] 

1172 if least_significant_label == _MINIMAL_OCTET: 

1173 return name.parent() 

1174 least_octet = least_significant_label[-1] 

1175 suffix_labels = name.labels[1:] 

1176 if least_octet == _MINIMAL_OCTET_VALUE: 

1177 new_labels = [least_significant_label[:-1]] 

1178 else: 

1179 octets = bytearray(least_significant_label) 

1180 octet = octets[-1] 

1181 if octet == _LEFT_SQUARE_BRACKET_VALUE: 

1182 octet = _AT_SIGN_VALUE 

1183 else: 

1184 octet -= 1 

1185 octets[-1] = octet 

1186 least_significant_label = bytes(octets) 

1187 new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)] 

1188 new_labels.extend(suffix_labels) 

1189 name = Name(new_labels) 

1190 if prefix_ok: 

1191 return _pad_to_max_name(name) 

1192 else: 

1193 return name 

1194 

1195 

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

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

1198 # 3.1.2. 

1199 # 

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

1201 # subdomain of origin. 

1202 if prefix_ok: 

1203 # Try prefixing \000 as new label 

1204 try: 

1205 return _SUCCESSOR_PREFIX.concatenate(name) 

1206 except NameTooLong: 

1207 pass 

1208 while name != origin: 

1209 # Try extending the least significant label. 

1210 least_significant_label = name[0] 

1211 if len(least_significant_label) < 63: 

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

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

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

1215 new_labels = [least_significant_label + _MINIMAL_OCTET] 

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

1217 try: 

1218 return Name(new_labels) 

1219 except NameTooLong: 

1220 pass 

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

1222 # signficant non-maximal byte in it. 

1223 octets = bytearray(least_significant_label) 

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

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

1226 # to the right of it. 

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

1228 octet = octets[i] 

1229 if octet == _MAXIMAL_OCTET_VALUE: 

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

1231 continue 

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

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

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

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

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

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

1238 if octet == _AT_SIGN_VALUE: 

1239 octet = _LEFT_SQUARE_BRACKET_VALUE 

1240 else: 

1241 octet += 1 

1242 octets[i] = octet 

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

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

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

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

1247 # always work. 

1248 return Name(new_labels) 

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

1250 # again. 

1251 name = name.parent() 

1252 

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

1254 # DNSSEC way. 

1255 return origin 

1256 

1257 

1258def _handle_relativity_and_call( 

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

1260 name: Name, 

1261 origin: Name, 

1262 prefix_ok: bool, 

1263) -> Name: 

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

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

1266 if not origin.is_absolute(): 

1267 raise NeedAbsoluteNameOrOrigin 

1268 relative = not name.is_absolute() 

1269 if relative: 

1270 name = name.derelativize(origin) 

1271 elif not name.is_subdomain(origin): 

1272 raise NeedSubdomainOfOrigin 

1273 result_name = function(name, origin, prefix_ok) 

1274 if relative: 

1275 result_name = result_name.relativize(origin) 

1276 return result_name