librpc: Shorten dcerpc_binding_handle_call a bit
[Samba/gebeck_regimport.git] / lib / dnspython / dns / rdata.py
blob350bf790b861942822094b734903711d23f2d151
1 # Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
3 # Permission to use, copy, modify, and distribute this software and its
4 # documentation for any purpose with or without fee is hereby granted,
5 # provided that the above copyright notice and this permission notice
6 # appear in all copies.
8 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
9 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
11 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
14 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 """DNS rdata.
18 @var _rdata_modules: A dictionary mapping a (rdclass, rdtype) tuple to
19 the module which implements that type.
20 @type _rdata_modules: dict
21 @var _module_prefix: The prefix to use when forming modules names. The
22 default is 'dns.rdtypes'. Changing this value will break the library.
23 @type _module_prefix: string
24 @var _hex_chunk: At most this many octets that will be represented in each
25 chunk of hexstring that _hexify() produces before whitespace occurs.
26 @type _hex_chunk: int"""
28 import cStringIO
30 import dns.exception
31 import dns.name
32 import dns.rdataclass
33 import dns.rdatatype
34 import dns.tokenizer
35 import dns.wiredata
37 _hex_chunksize = 32
39 def _hexify(data, chunksize=None):
40 """Convert a binary string into its hex encoding, broken up into chunks
41 of I{chunksize} characters separated by a space.
43 @param data: the binary string
44 @type data: string
45 @param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize}
46 @rtype: string
47 """
49 if chunksize is None:
50 chunksize = _hex_chunksize
51 hex = data.encode('hex_codec')
52 l = len(hex)
53 if l > chunksize:
54 chunks = []
55 i = 0
56 while i < l:
57 chunks.append(hex[i : i + chunksize])
58 i += chunksize
59 hex = ' '.join(chunks)
60 return hex
62 _base64_chunksize = 32
64 def _base64ify(data, chunksize=None):
65 """Convert a binary string into its base64 encoding, broken up into chunks
66 of I{chunksize} characters separated by a space.
68 @param data: the binary string
69 @type data: string
70 @param chunksize: the chunk size. Default is
71 L{dns.rdata._base64_chunksize}
72 @rtype: string
73 """
75 if chunksize is None:
76 chunksize = _base64_chunksize
77 b64 = data.encode('base64_codec')
78 b64 = b64.replace('\n', '')
79 l = len(b64)
80 if l > chunksize:
81 chunks = []
82 i = 0
83 while i < l:
84 chunks.append(b64[i : i + chunksize])
85 i += chunksize
86 b64 = ' '.join(chunks)
87 return b64
89 __escaped = {
90 '"' : True,
91 '\\' : True,
94 def _escapify(qstring):
95 """Escape the characters in a quoted string which need it.
97 @param qstring: the string
98 @type qstring: string
99 @returns: the escaped string
100 @rtype: string
103 text = ''
104 for c in qstring:
105 if c in __escaped:
106 text += '\\' + c
107 elif ord(c) >= 0x20 and ord(c) < 0x7F:
108 text += c
109 else:
110 text += '\\%03d' % ord(c)
111 return text
113 def _truncate_bitmap(what):
114 """Determine the index of greatest byte that isn't all zeros, and
115 return the bitmap that contains all the bytes less than that index.
117 @param what: a string of octets representing a bitmap.
118 @type what: string
119 @rtype: string
122 for i in xrange(len(what) - 1, -1, -1):
123 if what[i] != '\x00':
124 break
125 return ''.join(what[0 : i + 1])
127 class Rdata(object):
128 """Base class for all DNS rdata types.
131 __slots__ = ['rdclass', 'rdtype']
133 def __init__(self, rdclass, rdtype):
134 """Initialize an rdata.
135 @param rdclass: The rdata class
136 @type rdclass: int
137 @param rdtype: The rdata type
138 @type rdtype: int
141 self.rdclass = rdclass
142 self.rdtype = rdtype
144 def covers(self):
145 """DNS SIG/RRSIG rdatas apply to a specific type; this type is
146 returned by the covers() function. If the rdata type is not
147 SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when
148 creating rdatasets, allowing the rdataset to contain only RRSIGs
149 of a particular type, e.g. RRSIG(NS).
150 @rtype: int
153 return dns.rdatatype.NONE
155 def extended_rdatatype(self):
156 """Return a 32-bit type value, the least significant 16 bits of
157 which are the ordinary DNS type, and the upper 16 bits of which are
158 the "covered" type, if any.
159 @rtype: int
162 return self.covers() << 16 | self.rdtype
164 def to_text(self, origin=None, relativize=True, **kw):
165 """Convert an rdata to text format.
166 @rtype: string
168 raise NotImplementedError
170 def to_wire(self, file, compress = None, origin = None):
171 """Convert an rdata to wire format.
172 @rtype: string
175 raise NotImplementedError
177 def to_digestable(self, origin = None):
178 """Convert rdata to a format suitable for digesting in hashes. This
179 is also the DNSSEC canonical form."""
180 f = cStringIO.StringIO()
181 self.to_wire(f, None, origin)
182 return f.getvalue()
184 def validate(self):
185 """Check that the current contents of the rdata's fields are
186 valid. If you change an rdata by assigning to its fields,
187 it is a good idea to call validate() when you are done making
188 changes.
190 dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
192 def __repr__(self):
193 covers = self.covers()
194 if covers == dns.rdatatype.NONE:
195 ctext = ''
196 else:
197 ctext = '(' + dns.rdatatype.to_text(covers) + ')'
198 return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \
199 dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \
200 str(self) + '>'
202 def __str__(self):
203 return self.to_text()
205 def _cmp(self, other):
206 """Compare an rdata with another rdata of the same rdtype and
207 rdclass. Return < 0 if self < other in the DNSSEC ordering,
208 0 if self == other, and > 0 if self > other.
211 raise NotImplementedError
213 def __eq__(self, other):
214 if not isinstance(other, Rdata):
215 return False
216 if self.rdclass != other.rdclass or \
217 self.rdtype != other.rdtype:
218 return False
219 return self._cmp(other) == 0
221 def __ne__(self, other):
222 if not isinstance(other, Rdata):
223 return True
224 if self.rdclass != other.rdclass or \
225 self.rdtype != other.rdtype:
226 return True
227 return self._cmp(other) != 0
229 def __lt__(self, other):
230 if not isinstance(other, Rdata) or \
231 self.rdclass != other.rdclass or \
232 self.rdtype != other.rdtype:
233 return NotImplemented
234 return self._cmp(other) < 0
236 def __le__(self, other):
237 if not isinstance(other, Rdata) or \
238 self.rdclass != other.rdclass or \
239 self.rdtype != other.rdtype:
240 return NotImplemented
241 return self._cmp(other) <= 0
243 def __ge__(self, other):
244 if not isinstance(other, Rdata) or \
245 self.rdclass != other.rdclass or \
246 self.rdtype != other.rdtype:
247 return NotImplemented
248 return self._cmp(other) >= 0
250 def __gt__(self, other):
251 if not isinstance(other, Rdata) or \
252 self.rdclass != other.rdclass or \
253 self.rdtype != other.rdtype:
254 return NotImplemented
255 return self._cmp(other) > 0
257 def __hash__(self):
258 return hash(self.to_digestable(dns.name.root))
260 def _wire_cmp(self, other):
261 # A number of types compare rdata in wire form, so we provide
262 # the method here instead of duplicating it.
264 # We specifiy an arbitrary origin of '.' when doing the
265 # comparison, since the rdata may have relative names and we
266 # can't convert a relative name to wire without an origin.
267 b1 = cStringIO.StringIO()
268 self.to_wire(b1, None, dns.name.root)
269 b2 = cStringIO.StringIO()
270 other.to_wire(b2, None, dns.name.root)
271 return cmp(b1.getvalue(), b2.getvalue())
273 def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
274 """Build an rdata object from text format.
276 @param rdclass: The rdata class
277 @type rdclass: int
278 @param rdtype: The rdata type
279 @type rdtype: int
280 @param tok: The tokenizer
281 @type tok: dns.tokenizer.Tokenizer
282 @param origin: The origin to use for relative names
283 @type origin: dns.name.Name
284 @param relativize: should names be relativized?
285 @type relativize: bool
286 @rtype: dns.rdata.Rdata instance
289 raise NotImplementedError
291 from_text = classmethod(from_text)
293 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
294 """Build an rdata object from wire format
296 @param rdclass: The rdata class
297 @type rdclass: int
298 @param rdtype: The rdata type
299 @type rdtype: int
300 @param wire: The wire-format message
301 @type wire: string
302 @param current: The offet in wire of the beginning of the rdata.
303 @type current: int
304 @param rdlen: The length of the wire-format rdata
305 @type rdlen: int
306 @param origin: The origin to use for relative names
307 @type origin: dns.name.Name
308 @rtype: dns.rdata.Rdata instance
311 raise NotImplementedError
313 from_wire = classmethod(from_wire)
315 def choose_relativity(self, origin = None, relativize = True):
316 """Convert any domain names in the rdata to the specified
317 relativization.
320 pass
323 class GenericRdata(Rdata):
324 """Generate Rdata Class
326 This class is used for rdata types for which we have no better
327 implementation. It implements the DNS "unknown RRs" scheme.
330 __slots__ = ['data']
332 def __init__(self, rdclass, rdtype, data):
333 super(GenericRdata, self).__init__(rdclass, rdtype)
334 self.data = data
336 def to_text(self, origin=None, relativize=True, **kw):
337 return r'\# %d ' % len(self.data) + _hexify(self.data)
339 def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
340 token = tok.get()
341 if not token.is_identifier() or token.value != '\#':
342 raise dns.exception.SyntaxError(r'generic rdata does not start with \#')
343 length = tok.get_int()
344 chunks = []
345 while 1:
346 token = tok.get()
347 if token.is_eol_or_eof():
348 break
349 chunks.append(token.value)
350 hex = ''.join(chunks)
351 data = hex.decode('hex_codec')
352 if len(data) != length:
353 raise dns.exception.SyntaxError('generic rdata hex data has wrong length')
354 return cls(rdclass, rdtype, data)
356 from_text = classmethod(from_text)
358 def to_wire(self, file, compress = None, origin = None):
359 file.write(self.data)
361 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
362 return cls(rdclass, rdtype, wire[current : current + rdlen])
364 from_wire = classmethod(from_wire)
366 def _cmp(self, other):
367 return cmp(self.data, other.data)
369 _rdata_modules = {}
370 _module_prefix = 'dns.rdtypes'
372 def get_rdata_class(rdclass, rdtype):
374 def import_module(name):
375 mod = __import__(name)
376 components = name.split('.')
377 for comp in components[1:]:
378 mod = getattr(mod, comp)
379 return mod
381 mod = _rdata_modules.get((rdclass, rdtype))
382 rdclass_text = dns.rdataclass.to_text(rdclass)
383 rdtype_text = dns.rdatatype.to_text(rdtype)
384 rdtype_text = rdtype_text.replace('-', '_')
385 if not mod:
386 mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype))
387 if not mod:
388 try:
389 mod = import_module('.'.join([_module_prefix,
390 rdclass_text, rdtype_text]))
391 _rdata_modules[(rdclass, rdtype)] = mod
392 except ImportError:
393 try:
394 mod = import_module('.'.join([_module_prefix,
395 'ANY', rdtype_text]))
396 _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod
397 except ImportError:
398 mod = None
399 if mod:
400 cls = getattr(mod, rdtype_text)
401 else:
402 cls = GenericRdata
403 return cls
405 def from_text(rdclass, rdtype, tok, origin = None, relativize = True):
406 """Build an rdata object from text format.
408 This function attempts to dynamically load a class which
409 implements the specified rdata class and type. If there is no
410 class-and-type-specific implementation, the GenericRdata class
411 is used.
413 Once a class is chosen, its from_text() class method is called
414 with the parameters to this function.
416 If I{tok} is a string, then a tokenizer is created and the string
417 is used as its input.
419 @param rdclass: The rdata class
420 @type rdclass: int
421 @param rdtype: The rdata type
422 @type rdtype: int
423 @param tok: The tokenizer or input text
424 @type tok: dns.tokenizer.Tokenizer or string
425 @param origin: The origin to use for relative names
426 @type origin: dns.name.Name
427 @param relativize: Should names be relativized?
428 @type relativize: bool
429 @rtype: dns.rdata.Rdata instance"""
431 if isinstance(tok, str):
432 tok = dns.tokenizer.Tokenizer(tok)
433 cls = get_rdata_class(rdclass, rdtype)
434 if cls != GenericRdata:
435 # peek at first token
436 token = tok.get()
437 tok.unget(token)
438 if token.is_identifier() and \
439 token.value == r'\#':
441 # Known type using the generic syntax. Extract the
442 # wire form from the generic syntax, and then run
443 # from_wire on it.
445 rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
446 relativize)
447 return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
448 origin)
449 return cls.from_text(rdclass, rdtype, tok, origin, relativize)
451 def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None):
452 """Build an rdata object from wire format
454 This function attempts to dynamically load a class which
455 implements the specified rdata class and type. If there is no
456 class-and-type-specific implementation, the GenericRdata class
457 is used.
459 Once a class is chosen, its from_wire() class method is called
460 with the parameters to this function.
462 @param rdclass: The rdata class
463 @type rdclass: int
464 @param rdtype: The rdata type
465 @type rdtype: int
466 @param wire: The wire-format message
467 @type wire: string
468 @param current: The offet in wire of the beginning of the rdata.
469 @type current: int
470 @param rdlen: The length of the wire-format rdata
471 @type rdlen: int
472 @param origin: The origin to use for relative names
473 @type origin: dns.name.Name
474 @rtype: dns.rdata.Rdata instance"""
476 wire = dns.wiredata.maybe_wrap(wire)
477 cls = get_rdata_class(rdclass, rdtype)
478 return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)