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