rrset.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # Copyright (C) 2003-2017 Nominum, Inc.
  3. #
  4. # Permission to use, copy, modify, and distribute this software and its
  5. # documentation for any purpose with or without fee is hereby granted,
  6. # provided that the above copyright notice and this permission notice
  7. # appear in all copies.
  8. #
  9. # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
  10. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
  12. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  15. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. """DNS RRsets (an RRset is a named rdataset)"""
  17. from typing import Any, Collection, Dict, cast
  18. import dns.name
  19. import dns.rdata
  20. import dns.rdataclass
  21. import dns.rdataset
  22. import dns.rdatatype
  23. import dns.renderer
  24. class RRset(dns.rdataset.Rdataset):
  25. """A DNS RRset (named rdataset).
  26. RRset inherits from Rdataset, and RRsets can be treated as
  27. Rdatasets in most cases. There are, however, a few notable
  28. exceptions. RRsets have different to_wire() and to_text() method
  29. arguments, reflecting the fact that RRsets always have an owner
  30. name.
  31. """
  32. __slots__ = ["name", "deleting"]
  33. def __init__(
  34. self,
  35. name: dns.name.Name,
  36. rdclass: dns.rdataclass.RdataClass,
  37. rdtype: dns.rdatatype.RdataType,
  38. covers: dns.rdatatype.RdataType = dns.rdatatype.NONE,
  39. deleting: dns.rdataclass.RdataClass | None = None,
  40. ):
  41. """Create a new RRset."""
  42. super().__init__(rdclass, rdtype, covers)
  43. self.name = name
  44. self.deleting = deleting
  45. def _clone(self):
  46. obj = cast(RRset, super()._clone())
  47. obj.name = self.name
  48. obj.deleting = self.deleting
  49. return obj
  50. def __repr__(self):
  51. if self.covers == 0:
  52. ctext = ""
  53. else:
  54. ctext = "(" + dns.rdatatype.to_text(self.covers) + ")"
  55. if self.deleting is not None:
  56. dtext = " delete=" + dns.rdataclass.to_text(self.deleting)
  57. else:
  58. dtext = ""
  59. return (
  60. "<DNS "
  61. + str(self.name)
  62. + " "
  63. + dns.rdataclass.to_text(self.rdclass)
  64. + " "
  65. + dns.rdatatype.to_text(self.rdtype)
  66. + ctext
  67. + dtext
  68. + " RRset: "
  69. + self._rdata_repr()
  70. + ">"
  71. )
  72. def __str__(self):
  73. return self.to_text()
  74. def __eq__(self, other):
  75. if isinstance(other, RRset):
  76. if self.name != other.name:
  77. return False
  78. elif not isinstance(other, dns.rdataset.Rdataset):
  79. return False
  80. return super().__eq__(other)
  81. def match(self, *args: Any, **kwargs: Any) -> bool: # type: ignore[override]
  82. """Does this rrset match the specified attributes?
  83. Behaves as :py:func:`full_match()` if the first argument is a
  84. ``dns.name.Name``, and as :py:func:`dns.rdataset.Rdataset.match()`
  85. otherwise.
  86. (This behavior fixes a design mistake where the signature of this
  87. method became incompatible with that of its superclass. The fix
  88. makes RRsets matchable as Rdatasets while preserving backwards
  89. compatibility.)
  90. """
  91. if isinstance(args[0], dns.name.Name):
  92. return self.full_match(*args, **kwargs) # type: ignore[arg-type]
  93. else:
  94. return super().match(*args, **kwargs) # type: ignore[arg-type]
  95. def full_match(
  96. self,
  97. name: dns.name.Name,
  98. rdclass: dns.rdataclass.RdataClass,
  99. rdtype: dns.rdatatype.RdataType,
  100. covers: dns.rdatatype.RdataType,
  101. deleting: dns.rdataclass.RdataClass | None = None,
  102. ) -> bool:
  103. """Returns ``True`` if this rrset matches the specified name, class,
  104. type, covers, and deletion state.
  105. """
  106. if not super().match(rdclass, rdtype, covers):
  107. return False
  108. if self.name != name or self.deleting != deleting:
  109. return False
  110. return True
  111. # pylint: disable=arguments-differ
  112. def to_text( # type: ignore[override]
  113. self,
  114. origin: dns.name.Name | None = None,
  115. relativize: bool = True,
  116. **kw: Dict[str, Any],
  117. ) -> str:
  118. """Convert the RRset into DNS zone file format.
  119. See ``dns.name.Name.choose_relativity`` for more information
  120. on how *origin* and *relativize* determine the way names
  121. are emitted.
  122. Any additional keyword arguments are passed on to the rdata
  123. ``to_text()`` method.
  124. *origin*, a ``dns.name.Name`` or ``None``, the origin for relative
  125. names.
  126. *relativize*, a ``bool``. If ``True``, names will be relativized
  127. to *origin*.
  128. """
  129. return super().to_text(
  130. self.name, origin, relativize, self.deleting, **kw # type: ignore
  131. )
  132. def to_wire( # type: ignore[override]
  133. self,
  134. file: Any,
  135. compress: dns.name.CompressType | None = None, # type: ignore
  136. origin: dns.name.Name | None = None,
  137. **kw: Dict[str, Any],
  138. ) -> int:
  139. """Convert the RRset to wire format.
  140. All keyword arguments are passed to ``dns.rdataset.to_wire()``; see
  141. that function for details.
  142. Returns an ``int``, the number of records emitted.
  143. """
  144. return super().to_wire(
  145. self.name, file, compress, origin, self.deleting, **kw # type:ignore
  146. )
  147. # pylint: enable=arguments-differ
  148. def to_rdataset(self) -> dns.rdataset.Rdataset:
  149. """Convert an RRset into an Rdataset.
  150. Returns a ``dns.rdataset.Rdataset``.
  151. """
  152. return dns.rdataset.from_rdata_list(self.ttl, list(self))
  153. def from_text_list(
  154. name: dns.name.Name | str,
  155. ttl: int,
  156. rdclass: dns.rdataclass.RdataClass | str,
  157. rdtype: dns.rdatatype.RdataType | str,
  158. text_rdatas: Collection[str],
  159. idna_codec: dns.name.IDNACodec | None = None,
  160. origin: dns.name.Name | None = None,
  161. relativize: bool = True,
  162. relativize_to: dns.name.Name | None = None,
  163. ) -> RRset:
  164. """Create an RRset with the specified name, TTL, class, and type, and with
  165. the specified list of rdatas in text format.
  166. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
  167. encoder/decoder to use; if ``None``, the default IDNA 2003
  168. encoder/decoder is used.
  169. *origin*, a ``dns.name.Name`` (or ``None``), the
  170. origin to use for relative names.
  171. *relativize*, a ``bool``. If true, name will be relativized.
  172. *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use
  173. when relativizing names. If not set, the *origin* value will be used.
  174. Returns a ``dns.rrset.RRset`` object.
  175. """
  176. if isinstance(name, str):
  177. name = dns.name.from_text(name, None, idna_codec=idna_codec)
  178. rdclass = dns.rdataclass.RdataClass.make(rdclass)
  179. rdtype = dns.rdatatype.RdataType.make(rdtype)
  180. r = RRset(name, rdclass, rdtype)
  181. r.update_ttl(ttl)
  182. for t in text_rdatas:
  183. rd = dns.rdata.from_text(
  184. r.rdclass, r.rdtype, t, origin, relativize, relativize_to, idna_codec
  185. )
  186. r.add(rd)
  187. return r
  188. def from_text(
  189. name: dns.name.Name | str,
  190. ttl: int,
  191. rdclass: dns.rdataclass.RdataClass | str,
  192. rdtype: dns.rdatatype.RdataType | str,
  193. *text_rdatas: Any,
  194. ) -> RRset:
  195. """Create an RRset with the specified name, TTL, class, and type and with
  196. the specified rdatas in text format.
  197. Returns a ``dns.rrset.RRset`` object.
  198. """
  199. return from_text_list(
  200. name, ttl, rdclass, rdtype, cast(Collection[str], text_rdatas)
  201. )
  202. def from_rdata_list(
  203. name: dns.name.Name | str,
  204. ttl: int,
  205. rdatas: Collection[dns.rdata.Rdata],
  206. idna_codec: dns.name.IDNACodec | None = None,
  207. ) -> RRset:
  208. """Create an RRset with the specified name and TTL, and with
  209. the specified list of rdata objects.
  210. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
  211. encoder/decoder to use; if ``None``, the default IDNA 2003
  212. encoder/decoder is used.
  213. Returns a ``dns.rrset.RRset`` object.
  214. """
  215. if isinstance(name, str):
  216. name = dns.name.from_text(name, None, idna_codec=idna_codec)
  217. if len(rdatas) == 0:
  218. raise ValueError("rdata list must not be empty")
  219. r = None
  220. for rd in rdatas:
  221. if r is None:
  222. r = RRset(name, rd.rdclass, rd.rdtype)
  223. r.update_ttl(ttl)
  224. r.add(rd)
  225. assert r is not None
  226. return r
  227. def from_rdata(name: dns.name.Name | str, ttl: int, *rdatas: Any) -> RRset:
  228. """Create an RRset with the specified name and TTL, and with
  229. the specified rdata objects.
  230. Returns a ``dns.rrset.RRset`` object.
  231. """
  232. return from_rdata_list(name, ttl, cast(Collection[dns.rdata.Rdata], rdatas))