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