rrset.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. import dns.name
  18. import dns.rdataset
  19. import dns.rdataclass
  20. import dns.renderer
  21. class RRset(dns.rdataset.Rdataset):
  22. """A DNS RRset (named rdataset).
  23. RRset inherits from Rdataset, and RRsets can be treated as
  24. Rdatasets in most cases. There are, however, a few notable
  25. exceptions. RRsets have different to_wire() and to_text() method
  26. arguments, reflecting the fact that RRsets always have an owner
  27. name.
  28. """
  29. __slots__ = ['name', 'deleting']
  30. def __init__(self, name, rdclass, rdtype, covers=dns.rdatatype.NONE,
  31. deleting=None):
  32. """Create a new RRset."""
  33. super().__init__(rdclass, rdtype, covers)
  34. self.name = name
  35. self.deleting = deleting
  36. def _clone(self):
  37. obj = super()._clone()
  38. obj.name = self.name
  39. obj.deleting = self.deleting
  40. return obj
  41. def __repr__(self):
  42. if self.covers == 0:
  43. ctext = ''
  44. else:
  45. ctext = '(' + dns.rdatatype.to_text(self.covers) + ')'
  46. if self.deleting is not None:
  47. dtext = ' delete=' + dns.rdataclass.to_text(self.deleting)
  48. else:
  49. dtext = ''
  50. return '<DNS ' + str(self.name) + ' ' + \
  51. dns.rdataclass.to_text(self.rdclass) + ' ' + \
  52. dns.rdatatype.to_text(self.rdtype) + ctext + dtext + \
  53. ' RRset: ' + self._rdata_repr() + '>'
  54. def __str__(self):
  55. return self.to_text()
  56. def __eq__(self, other):
  57. if isinstance(other, RRset):
  58. if self.name != other.name:
  59. return False
  60. elif not isinstance(other, dns.rdataset.Rdataset):
  61. return False
  62. return super().__eq__(other)
  63. def match(self, *args, **kwargs):
  64. """Does this rrset match the specified attributes?
  65. Behaves as :py:func:`full_match()` if the first argument is a
  66. ``dns.name.Name``, and as :py:func:`dns.rdataset.Rdataset.match()`
  67. otherwise.
  68. (This behavior fixes a design mistake where the signature of this
  69. method became incompatible with that of its superclass. The fix
  70. makes RRsets matchable as Rdatasets while preserving backwards
  71. compatibility.)
  72. """
  73. if isinstance(args[0], dns.name.Name):
  74. return self.full_match(*args, **kwargs)
  75. else:
  76. return super().match(*args, **kwargs)
  77. def full_match(self, name, rdclass, rdtype, covers,
  78. deleting=None):
  79. """Returns ``True`` if this rrset matches the specified name, class,
  80. type, covers, and deletion state.
  81. """
  82. if not super().match(rdclass, rdtype, covers):
  83. return False
  84. if self.name != name or self.deleting != deleting:
  85. return False
  86. return True
  87. # pylint: disable=arguments-differ
  88. def to_text(self, origin=None, relativize=True, **kw):
  89. """Convert the RRset into DNS zone file format.
  90. See ``dns.name.Name.choose_relativity`` for more information
  91. on how *origin* and *relativize* determine the way names
  92. are emitted.
  93. Any additional keyword arguments are passed on to the rdata
  94. ``to_text()`` method.
  95. *origin*, a ``dns.name.Name`` or ``None``, the origin for relative
  96. names.
  97. *relativize*, a ``bool``. If ``True``, names will be relativized
  98. to *origin*.
  99. """
  100. return super().to_text(self.name, origin, relativize,
  101. self.deleting, **kw)
  102. def to_wire(self, file, compress=None, origin=None,
  103. **kw):
  104. """Convert the RRset to wire format.
  105. All keyword arguments are passed to ``dns.rdataset.to_wire()``; see
  106. that function for details.
  107. Returns an ``int``, the number of records emitted.
  108. """
  109. return super().to_wire(self.name, file, compress, origin,
  110. self.deleting, **kw)
  111. # pylint: enable=arguments-differ
  112. def to_rdataset(self):
  113. """Convert an RRset into an Rdataset.
  114. Returns a ``dns.rdataset.Rdataset``.
  115. """
  116. return dns.rdataset.from_rdata_list(self.ttl, list(self))
  117. def from_text_list(name, ttl, rdclass, rdtype, text_rdatas,
  118. idna_codec=None, origin=None, relativize=True,
  119. relativize_to=None):
  120. """Create an RRset with the specified name, TTL, class, and type, and with
  121. the specified list of rdatas in text format.
  122. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
  123. encoder/decoder to use; if ``None``, the default IDNA 2003
  124. encoder/decoder is used.
  125. *origin*, a ``dns.name.Name`` (or ``None``), the
  126. origin to use for relative names.
  127. *relativize*, a ``bool``. If true, name will be relativized.
  128. *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use
  129. when relativizing names. If not set, the *origin* value will be used.
  130. Returns a ``dns.rrset.RRset`` object.
  131. """
  132. if isinstance(name, str):
  133. name = dns.name.from_text(name, None, idna_codec=idna_codec)
  134. rdclass = dns.rdataclass.RdataClass.make(rdclass)
  135. rdtype = dns.rdatatype.RdataType.make(rdtype)
  136. r = RRset(name, rdclass, rdtype)
  137. r.update_ttl(ttl)
  138. for t in text_rdatas:
  139. rd = dns.rdata.from_text(r.rdclass, r.rdtype, t, origin, relativize,
  140. relativize_to, idna_codec)
  141. r.add(rd)
  142. return r
  143. def from_text(name, ttl, rdclass, rdtype, *text_rdatas):
  144. """Create an RRset with the specified name, TTL, class, and type and with
  145. the specified rdatas in text format.
  146. Returns a ``dns.rrset.RRset`` object.
  147. """
  148. return from_text_list(name, ttl, rdclass, rdtype, text_rdatas)
  149. def from_rdata_list(name, ttl, rdatas, idna_codec=None):
  150. """Create an RRset with the specified name and TTL, and with
  151. the specified list of rdata objects.
  152. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
  153. encoder/decoder to use; if ``None``, the default IDNA 2003
  154. encoder/decoder is used.
  155. Returns a ``dns.rrset.RRset`` object.
  156. """
  157. if isinstance(name, str):
  158. name = dns.name.from_text(name, None, idna_codec=idna_codec)
  159. if len(rdatas) == 0:
  160. raise ValueError("rdata list must not be empty")
  161. r = None
  162. for rd in rdatas:
  163. if r is None:
  164. r = RRset(name, rd.rdclass, rd.rdtype)
  165. r.update_ttl(ttl)
  166. r.add(rd)
  167. return r
  168. def from_rdata(name, ttl, *rdatas):
  169. """Create an RRset with the specified name and TTL, and with
  170. the specified rdata objects.
  171. Returns a ``dns.rrset.RRset`` object.
  172. """
  173. return from_rdata_list(name, ttl, rdatas)