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.
18 @var root: The DNS root name.
19 @type root: dns.name.Name object
20 @var empty: The empty DNS name.
21 @type empty: dns.name.Name object
28 if sys
.hexversion
>= 0x02030000:
35 NAMERELN_SUPERDOMAIN
= 1
36 NAMERELN_SUBDOMAIN
= 2
38 NAMERELN_COMMONANCESTOR
= 4
40 class EmptyLabel(dns
.exception
.SyntaxError):
41 """Raised if a label is empty."""
44 class BadEscape(dns
.exception
.SyntaxError):
45 """Raised if an escaped code in a text format name is invalid."""
48 class BadPointer(dns
.exception
.FormError
):
49 """Raised if a compression pointer points forward instead of backward."""
52 class BadLabelType(dns
.exception
.FormError
):
53 """Raised if the label type of a wire format name is unknown."""
56 class NeedAbsoluteNameOrOrigin(dns
.exception
.DNSException
):
57 """Raised if an attempt is made to convert a non-absolute name to
58 wire when there is also a non-absolute (or missing) origin."""
61 class NameTooLong(dns
.exception
.FormError
):
62 """Raised if a name is > 255 octets long."""
65 class LabelTooLong(dns
.exception
.SyntaxError):
66 """Raised if a label is > 63 octets long."""
69 class AbsoluteConcatenation(dns
.exception
.DNSException
):
70 """Raised if an attempt is made to append anything other than the
71 empty name to an absolute name."""
74 class NoParent(dns
.exception
.DNSException
):
75 """Raised if an attempt is made to get the parent of the root name
91 """Escape the characters in label which need it.
92 @returns: the escaped string
98 elif ord(c
) > 0x20 and ord(c
) < 0x7F:
101 text
+= '\\%03d' % ord(c
)
104 def _validate_labels(labels
):
105 """Check for empty labels in the middle of a label sequence,
106 labels that are too long, and for too many labels.
107 @raises NameTooLong: the name as a whole is too long
108 @raises LabelTooLong: an individual label is too long
109 @raises EmptyLabel: a label is empty (i.e. the root label) and appears
110 in a position other than the end of the label sequence"""
121 if i
< 0 and label
== '':
126 if i
>= 0 and i
!= l
- 1:
132 The dns.name.Name class represents a DNS name as a tuple of labels.
133 Instances of the class are immutable.
135 @ivar labels: The tuple of labels in the name. Each label is a string of
138 __slots__
= ['labels']
140 def __init__(self
, labels
):
141 """Initialize a domain name from a list of labels.
142 @param labels: the labels
143 @type labels: any iterable whose values are strings
146 super(Name
, self
).__setattr
__('labels', tuple(labels
))
147 _validate_labels(self
.labels
)
149 def __setattr__(self
, name
, value
):
150 raise TypeError("object doesn't support attribute assignment")
152 def is_absolute(self
):
153 """Is the most significant label of this name the root label?
157 return len(self
.labels
) > 0 and self
.labels
[-1] == ''
160 """Is this name wild? (I.e. Is the least significant label '*'?)
164 return len(self
.labels
) > 0 and self
.labels
[0] == '*'
167 """Return a case-insensitive hash of the name.
172 for label
in self
.labels
:
174 h
+= ( h
<< 3 ) + ord(c
.lower())
175 return int(h
% sys
.maxint
)
177 def fullcompare(self
, other
):
178 """Compare two names, returning a 3-tuple (relation, order, nlabels).
180 I{relation} describes the relation ship beween the names,
181 and is one of: dns.name.NAMERELN_NONE,
182 dns.name.NAMERELN_SUPERDOMAIN, dns.name.NAMERELN_SUBDOMAIN,
183 dns.name.NAMERELN_EQUAL, or dns.name.NAMERELN_COMMONANCESTOR
185 I{order} is < 0 if self < other, > 0 if self > other, and ==
186 0 if self == other. A relative name is always less than an
187 absolute name. If both names have the same relativity, then
188 the DNSSEC order relation is used to order them.
190 I{nlabels} is the number of significant labels that the two names
194 sabs
= self
.is_absolute()
195 oabs
= other
.is_absolute()
198 return (NAMERELN_NONE
, 1, 0)
200 return (NAMERELN_NONE
, -1, 0)
201 l1
= len(self
.labels
)
202 l2
= len(other
.labels
)
211 namereln
= NAMERELN_NONE
216 label1
= self
.labels
[l1
].lower()
217 label2
= other
.labels
[l2
].lower()
221 namereln
= NAMERELN_COMMONANCESTOR
222 return (namereln
, order
, nlabels
)
223 elif label1
> label2
:
226 namereln
= NAMERELN_COMMONANCESTOR
227 return (namereln
, order
, nlabels
)
231 namereln
= NAMERELN_SUPERDOMAIN
233 namereln
= NAMERELN_SUBDOMAIN
235 namereln
= NAMERELN_EQUAL
236 return (namereln
, order
, nlabels
)
238 def is_subdomain(self
, other
):
239 """Is self a subdomain of other?
241 The notion of subdomain includes equality.
245 (nr
, o
, nl
) = self
.fullcompare(other
)
246 if nr
== NAMERELN_SUBDOMAIN
or nr
== NAMERELN_EQUAL
:
250 def is_superdomain(self
, other
):
251 """Is self a superdomain of other?
253 The notion of subdomain includes equality.
257 (nr
, o
, nl
) = self
.fullcompare(other
)
258 if nr
== NAMERELN_SUPERDOMAIN
or nr
== NAMERELN_EQUAL
:
262 def canonicalize(self
):
263 """Return a name which is equal to the current name, but is in
264 DNSSEC canonical form.
265 @rtype: dns.name.Name object
268 return Name([x
.lower() for x
in self
.labels
])
270 def __eq__(self
, other
):
271 if isinstance(other
, Name
):
272 return self
.fullcompare(other
)[1] == 0
276 def __ne__(self
, other
):
277 if isinstance(other
, Name
):
278 return self
.fullcompare(other
)[1] != 0
282 def __lt__(self
, other
):
283 if isinstance(other
, Name
):
284 return self
.fullcompare(other
)[1] < 0
286 return NotImplemented
288 def __le__(self
, other
):
289 if isinstance(other
, Name
):
290 return self
.fullcompare(other
)[1] <= 0
292 return NotImplemented
294 def __ge__(self
, other
):
295 if isinstance(other
, Name
):
296 return self
.fullcompare(other
)[1] >= 0
298 return NotImplemented
300 def __gt__(self
, other
):
301 if isinstance(other
, Name
):
302 return self
.fullcompare(other
)[1] > 0
304 return NotImplemented
307 return '<DNS name ' + self
.__str
__() + '>'
310 return self
.to_text(False)
312 def to_text(self
, omit_final_dot
= False):
313 """Convert name to text format.
314 @param omit_final_dot: If True, don't emit the final dot (denoting the
315 root label) for absolute names. The default is False.
319 if len(self
.labels
) == 0:
321 if len(self
.labels
) == 1 and self
.labels
[0] == '':
323 if omit_final_dot
and self
.is_absolute():
327 s
= '.'.join(map(_escapify
, l
))
330 def to_unicode(self
, omit_final_dot
= False):
331 """Convert name to Unicode text format.
333 IDN ACE lables are converted to Unicode.
335 @param omit_final_dot: If True, don't emit the final dot (denoting the
336 root label) for absolute names. The default is False.
340 if len(self
.labels
) == 0:
342 if len(self
.labels
) == 1 and self
.labels
[0] == '':
344 if omit_final_dot
and self
.is_absolute():
348 s
= u
'.'.join([encodings
.idna
.ToUnicode(_escapify(x
)) for x
in l
])
351 def to_digestable(self
, origin
=None):
352 """Convert name to a format suitable for digesting in hashes.
354 The name is canonicalized and converted to uncompressed wire format.
356 @param origin: If the name is relative and origin is not None, then
357 origin will be appended to it.
358 @type origin: dns.name.Name object
359 @raises NeedAbsoluteNameOrOrigin: All names in wire format are
360 absolute. If self is a relative name, then an origin must be supplied;
361 if it is missing, then this exception is raised
365 if not self
.is_absolute():
366 if origin
is None or not origin
.is_absolute():
367 raise NeedAbsoluteNameOrOrigin
368 labels
= list(self
.labels
)
369 labels
.extend(list(origin
.labels
))
372 dlabels
= ["%s%s" % (chr(len(x
)), x
.lower()) for x
in labels
]
373 return ''.join(dlabels
)
375 def to_wire(self
, file = None, compress
= None, origin
= None):
376 """Convert name to wire format, possibly compressing it.
378 @param file: the file where the name is emitted (typically
379 a cStringIO file). If None, a string containing the wire name
381 @type file: file or None
382 @param compress: The compression table. If None (the default) names
383 will not be compressed.
385 @param origin: If the name is relative and origin is not None, then
386 origin will be appended to it.
387 @type origin: dns.name.Name object
388 @raises NeedAbsoluteNameOrOrigin: All names in wire format are
389 absolute. If self is a relative name, then an origin must be supplied;
390 if it is missing, then this exception is raised
394 file = cStringIO
.StringIO()
399 if not self
.is_absolute():
400 if origin
is None or not origin
.is_absolute():
401 raise NeedAbsoluteNameOrOrigin
402 labels
= list(self
.labels
)
403 labels
.extend(list(origin
.labels
))
410 if not compress
is None:
411 pos
= compress
.get(n
)
416 s
= struct
.pack('!H', value
)
420 if not compress
is None and len(n
) > 1:
429 return file.getvalue()
432 """The length of the name (in labels).
436 return len(self
.labels
)
438 def __getitem__(self
, index
):
439 return self
.labels
[index
]
441 def __getslice__(self
, start
, stop
):
442 return self
.labels
[start
:stop
]
444 def __add__(self
, other
):
445 return self
.concatenate(other
)
447 def __sub__(self
, other
):
448 return self
.relativize(other
)
450 def split(self
, depth
):
451 """Split a name into a prefix and suffix at depth.
453 @param depth: the number of labels in the suffix
455 @raises ValueError: the depth was not >= 0 and <= the length of the
457 @returns: the tuple (prefix, suffix)
463 return (self
, dns
.name
.empty
)
465 return (dns
.name
.empty
, self
)
466 elif depth
< 0 or depth
> l
:
467 raise ValueError('depth must be >= 0 and <= the length of the name')
468 return (Name(self
[: -depth
]), Name(self
[-depth
:]))
470 def concatenate(self
, other
):
471 """Return a new name which is the concatenation of self and other.
472 @rtype: dns.name.Name object
473 @raises AbsoluteConcatenation: self is absolute and other is
477 if self
.is_absolute() and len(other
) > 0:
478 raise AbsoluteConcatenation
479 labels
= list(self
.labels
)
480 labels
.extend(list(other
.labels
))
483 def relativize(self
, origin
):
484 """If self is a subdomain of origin, return a new name which is self
485 relative to origin. Otherwise return self.
486 @rtype: dns.name.Name object
489 if not origin
is None and self
.is_subdomain(origin
):
490 return Name(self
[: -len(origin
)])
494 def derelativize(self
, origin
):
495 """If self is a relative name, return a new name which is the
496 concatenation of self and origin. Otherwise return self.
497 @rtype: dns.name.Name object
500 if not self
.is_absolute():
501 return self
.concatenate(origin
)
505 def choose_relativity(self
, origin
=None, relativize
=True):
506 """Return a name with the relativity desired by the caller. If
507 origin is None, then self is returned. Otherwise, if
508 relativize is true the name is relativized, and if relativize is
509 false the name is derelativized.
510 @rtype: dns.name.Name object
515 return self
.relativize(origin
)
517 return self
.derelativize(origin
)
522 """Return the parent of the name.
523 @rtype: dns.name.Name object
524 @raises NoParent: the name is either the root name or the empty name,
525 and thus has no parent.
527 if self
== root
or self
== empty
:
529 return Name(self
.labels
[1:])
534 def from_unicode(text
, origin
= root
):
535 """Convert unicode text into a Name object.
537 Lables are encoded in IDN ACE form.
539 @rtype: dns.name.Name object
542 if not isinstance(text
, unicode):
543 raise ValueError("input to from_unicode() must be a unicode string")
544 if not (origin
is None or isinstance(origin
, Name
)):
545 raise ValueError("origin must be a Name or None")
555 return Name(['']) # no Unicode "u" on this constant!
574 elif c
== u
'.' or c
== u
'\u3002' or \
575 c
== u
'\uff0e' or c
== u
'\uff61':
578 labels
.append(encodings
.idna
.ToASCII(label
))
589 labels
.append(encodings
.idna
.ToASCII(label
))
592 if (len(labels
) == 0 or labels
[-1] != '') and not origin
is None:
593 labels
.extend(list(origin
.labels
))
596 def from_text(text
, origin
= root
):
597 """Convert text into a Name object.
598 @rtype: dns.name.Name object
601 if not isinstance(text
, str):
602 if isinstance(text
, unicode) and sys
.hexversion
>= 0x02030000:
603 return from_unicode(text
, origin
)
605 raise ValueError("input to from_text() must be a string")
606 if not (origin
is None or isinstance(origin
, Name
)):
607 raise ValueError("origin must be a Name or None")
653 if (len(labels
) == 0 or labels
[-1] != '') and not origin
is None:
654 labels
.extend(list(origin
.labels
))
657 def from_wire(message
, current
):
658 """Convert possibly compressed wire format into a Name.
659 @param message: the entire DNS message
660 @type message: string
661 @param current: the offset of the beginning of the name from the start
664 @raises dns.name.BadPointer: a compression pointer did not point backwards
666 @raises dns.name.BadLabelType: an invalid label type was encountered.
667 @returns: a tuple consisting of the name that was read and the number
668 of bytes of the wire format message which were consumed reading it
669 @rtype: (dns.name.Name object, int) tuple
672 if not isinstance(message
, str):
673 raise ValueError("input to from_wire() must be a byte string")
674 message
= dns
.wiredata
.maybe_wrap(message
)
676 biggest_pointer
= current
678 count
= ord(message
[current
])
683 labels
.append(message
[current
: current
+ count
].unwrap())
688 current
= (count
& 0x3f) * 256 + ord(message
[current
])
691 if current
>= biggest_pointer
:
693 biggest_pointer
= current
697 count
= ord(message
[current
])
702 return (Name(labels
), cused
)