1 # Copyright (C) 2001-2007, 2009, 2010 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:
34 NAMERELN_SUPERDOMAIN
= 1
35 NAMERELN_SUBDOMAIN
= 2
37 NAMERELN_COMMONANCESTOR
= 4
39 class EmptyLabel(dns
.exception
.SyntaxError):
40 """Raised if a label is empty."""
43 class BadEscape(dns
.exception
.SyntaxError):
44 """Raised if an escaped code in a text format name is invalid."""
47 class BadPointer(dns
.exception
.FormError
):
48 """Raised if a compression pointer points forward instead of backward."""
51 class BadLabelType(dns
.exception
.FormError
):
52 """Raised if the label type of a wire format name is unknown."""
55 class NeedAbsoluteNameOrOrigin(dns
.exception
.DNSException
):
56 """Raised if an attempt is made to convert a non-absolute name to
57 wire when there is also a non-absolute (or missing) origin."""
60 class NameTooLong(dns
.exception
.FormError
):
61 """Raised if a name is > 255 octets long."""
64 class LabelTooLong(dns
.exception
.SyntaxError):
65 """Raised if a label is > 63 octets long."""
68 class AbsoluteConcatenation(dns
.exception
.DNSException
):
69 """Raised if an attempt is made to append anything other than the
70 empty name to an absolute name."""
73 class NoParent(dns
.exception
.DNSException
):
74 """Raised if an attempt is made to get the parent of the root name
90 """Escape the characters in label which need it.
91 @returns: the escaped string
97 elif ord(c
) > 0x20 and ord(c
) < 0x7F:
100 text
+= '\\%03d' % ord(c
)
103 def _validate_labels(labels
):
104 """Check for empty labels in the middle of a label sequence,
105 labels that are too long, and for too many labels.
106 @raises NameTooLong: the name as a whole is too long
107 @raises LabelTooLong: an individual label is too long
108 @raises EmptyLabel: a label is empty (i.e. the root label) and appears
109 in a position other than the end of the label sequence"""
120 if i
< 0 and label
== '':
125 if i
>= 0 and i
!= l
- 1:
131 The dns.name.Name class represents a DNS name as a tuple of labels.
132 Instances of the class are immutable.
134 @ivar labels: The tuple of labels in the name. Each label is a string of
137 __slots__
= ['labels']
139 def __init__(self
, labels
):
140 """Initialize a domain name from a list of labels.
141 @param labels: the labels
142 @type labels: any iterable whose values are strings
145 super(Name
, self
).__setattr
__('labels', tuple(labels
))
146 _validate_labels(self
.labels
)
148 def __setattr__(self
, name
, value
):
149 raise TypeError("object doesn't support attribute assignment")
151 def is_absolute(self
):
152 """Is the most significant label of this name the root label?
156 return len(self
.labels
) > 0 and self
.labels
[-1] == ''
159 """Is this name wild? (I.e. Is the least significant label '*'?)
163 return len(self
.labels
) > 0 and self
.labels
[0] == '*'
166 """Return a case-insensitive hash of the name.
171 for label
in self
.labels
:
173 h
+= ( h
<< 3 ) + ord(c
.lower())
174 return int(h
% sys
.maxint
)
176 def fullcompare(self
, other
):
177 """Compare two names, returning a 3-tuple (relation, order, nlabels).
179 I{relation} describes the relation ship beween the names,
180 and is one of: dns.name.NAMERELN_NONE,
181 dns.name.NAMERELN_SUPERDOMAIN, dns.name.NAMERELN_SUBDOMAIN,
182 dns.name.NAMERELN_EQUAL, or dns.name.NAMERELN_COMMONANCESTOR
184 I{order} is < 0 if self < other, > 0 if self > other, and ==
185 0 if self == other. A relative name is always less than an
186 absolute name. If both names have the same relativity, then
187 the DNSSEC order relation is used to order them.
189 I{nlabels} is the number of significant labels that the two names
193 sabs
= self
.is_absolute()
194 oabs
= other
.is_absolute()
197 return (NAMERELN_NONE
, 1, 0)
199 return (NAMERELN_NONE
, -1, 0)
200 l1
= len(self
.labels
)
201 l2
= len(other
.labels
)
210 namereln
= NAMERELN_NONE
215 label1
= self
.labels
[l1
].lower()
216 label2
= other
.labels
[l2
].lower()
220 namereln
= NAMERELN_COMMONANCESTOR
221 return (namereln
, order
, nlabels
)
222 elif label1
> label2
:
225 namereln
= NAMERELN_COMMONANCESTOR
226 return (namereln
, order
, nlabels
)
230 namereln
= NAMERELN_SUPERDOMAIN
232 namereln
= NAMERELN_SUBDOMAIN
234 namereln
= NAMERELN_EQUAL
235 return (namereln
, order
, nlabels
)
237 def is_subdomain(self
, other
):
238 """Is self a subdomain of other?
240 The notion of subdomain includes equality.
244 (nr
, o
, nl
) = self
.fullcompare(other
)
245 if nr
== NAMERELN_SUBDOMAIN
or nr
== NAMERELN_EQUAL
:
249 def is_superdomain(self
, other
):
250 """Is self a superdomain of other?
252 The notion of subdomain includes equality.
256 (nr
, o
, nl
) = self
.fullcompare(other
)
257 if nr
== NAMERELN_SUPERDOMAIN
or nr
== NAMERELN_EQUAL
:
261 def canonicalize(self
):
262 """Return a name which is equal to the current name, but is in
263 DNSSEC canonical form.
264 @rtype: dns.name.Name object
267 return Name([x
.lower() for x
in self
.labels
])
269 def __eq__(self
, other
):
270 if isinstance(other
, Name
):
271 return self
.fullcompare(other
)[1] == 0
275 def __ne__(self
, other
):
276 if isinstance(other
, Name
):
277 return self
.fullcompare(other
)[1] != 0
281 def __lt__(self
, other
):
282 if isinstance(other
, Name
):
283 return self
.fullcompare(other
)[1] < 0
285 return NotImplemented
287 def __le__(self
, other
):
288 if isinstance(other
, Name
):
289 return self
.fullcompare(other
)[1] <= 0
291 return NotImplemented
293 def __ge__(self
, other
):
294 if isinstance(other
, Name
):
295 return self
.fullcompare(other
)[1] >= 0
297 return NotImplemented
299 def __gt__(self
, other
):
300 if isinstance(other
, Name
):
301 return self
.fullcompare(other
)[1] > 0
303 return NotImplemented
306 return '<DNS name ' + self
.__str
__() + '>'
309 return self
.to_text(False)
311 def to_text(self
, omit_final_dot
= False):
312 """Convert name to text format.
313 @param omit_final_dot: If True, don't emit the final dot (denoting the
314 root label) for absolute names. The default is False.
318 if len(self
.labels
) == 0:
320 if len(self
.labels
) == 1 and self
.labels
[0] == '':
322 if omit_final_dot
and self
.is_absolute():
326 s
= '.'.join(map(_escapify
, l
))
329 def to_unicode(self
, omit_final_dot
= False):
330 """Convert name to Unicode text format.
332 IDN ACE lables are converted to Unicode.
334 @param omit_final_dot: If True, don't emit the final dot (denoting the
335 root label) for absolute names. The default is False.
339 if len(self
.labels
) == 0:
341 if len(self
.labels
) == 1 and self
.labels
[0] == '':
343 if omit_final_dot
and self
.is_absolute():
347 s
= u
'.'.join([encodings
.idna
.ToUnicode(_escapify(x
)) for x
in l
])
350 def to_digestable(self
, origin
=None):
351 """Convert name to a format suitable for digesting in hashes.
353 The name is canonicalized and converted to uncompressed wire format.
355 @param origin: If the name is relative and origin is not None, then
356 origin will be appended to it.
357 @type origin: dns.name.Name object
358 @raises NeedAbsoluteNameOrOrigin: All names in wire format are
359 absolute. If self is a relative name, then an origin must be supplied;
360 if it is missing, then this exception is raised
364 if not self
.is_absolute():
365 if origin
is None or not origin
.is_absolute():
366 raise NeedAbsoluteNameOrOrigin
367 labels
= list(self
.labels
)
368 labels
.extend(list(origin
.labels
))
371 dlabels
= ["%s%s" % (chr(len(x
)), x
.lower()) for x
in labels
]
372 return ''.join(dlabels
)
374 def to_wire(self
, file = None, compress
= None, origin
= None):
375 """Convert name to wire format, possibly compressing it.
377 @param file: the file where the name is emitted (typically
378 a cStringIO file). If None, a string containing the wire name
380 @type file: file or None
381 @param compress: The compression table. If None (the default) names
382 will not be compressed.
384 @param origin: If the name is relative and origin is not None, then
385 origin will be appended to it.
386 @type origin: dns.name.Name object
387 @raises NeedAbsoluteNameOrOrigin: All names in wire format are
388 absolute. If self is a relative name, then an origin must be supplied;
389 if it is missing, then this exception is raised
393 file = cStringIO
.StringIO()
398 if not self
.is_absolute():
399 if origin
is None or not origin
.is_absolute():
400 raise NeedAbsoluteNameOrOrigin
401 labels
= list(self
.labels
)
402 labels
.extend(list(origin
.labels
))
409 if not compress
is None:
410 pos
= compress
.get(n
)
415 s
= struct
.pack('!H', value
)
419 if not compress
is None and len(n
) > 1:
428 return file.getvalue()
431 """The length of the name (in labels).
435 return len(self
.labels
)
437 def __getitem__(self
, index
):
438 return self
.labels
[index
]
440 def __getslice__(self
, start
, stop
):
441 return self
.labels
[start
:stop
]
443 def __add__(self
, other
):
444 return self
.concatenate(other
)
446 def __sub__(self
, other
):
447 return self
.relativize(other
)
449 def split(self
, depth
):
450 """Split a name into a prefix and suffix at depth.
452 @param depth: the number of labels in the suffix
454 @raises ValueError: the depth was not >= 0 and <= the length of the
456 @returns: the tuple (prefix, suffix)
462 return (self
, dns
.name
.empty
)
464 return (dns
.name
.empty
, self
)
465 elif depth
< 0 or depth
> l
:
466 raise ValueError('depth must be >= 0 and <= the length of the name')
467 return (Name(self
[: -depth
]), Name(self
[-depth
:]))
469 def concatenate(self
, other
):
470 """Return a new name which is the concatenation of self and other.
471 @rtype: dns.name.Name object
472 @raises AbsoluteConcatenation: self is absolute and other is
476 if self
.is_absolute() and len(other
) > 0:
477 raise AbsoluteConcatenation
478 labels
= list(self
.labels
)
479 labels
.extend(list(other
.labels
))
482 def relativize(self
, origin
):
483 """If self is a subdomain of origin, return a new name which is self
484 relative to origin. Otherwise return self.
485 @rtype: dns.name.Name object
488 if not origin
is None and self
.is_subdomain(origin
):
489 return Name(self
[: -len(origin
)])
493 def derelativize(self
, origin
):
494 """If self is a relative name, return a new name which is the
495 concatenation of self and origin. Otherwise return self.
496 @rtype: dns.name.Name object
499 if not self
.is_absolute():
500 return self
.concatenate(origin
)
504 def choose_relativity(self
, origin
=None, relativize
=True):
505 """Return a name with the relativity desired by the caller. If
506 origin is None, then self is returned. Otherwise, if
507 relativize is true the name is relativized, and if relativize is
508 false the name is derelativized.
509 @rtype: dns.name.Name object
514 return self
.relativize(origin
)
516 return self
.derelativize(origin
)
521 """Return the parent of the name.
522 @rtype: dns.name.Name object
523 @raises NoParent: the name is either the root name or the empty name,
524 and thus has no parent.
526 if self
== root
or self
== empty
:
528 return Name(self
.labels
[1:])
533 def from_unicode(text
, origin
= root
):
534 """Convert unicode text into a Name object.
536 Lables are encoded in IDN ACE form.
538 @rtype: dns.name.Name object
541 if not isinstance(text
, unicode):
542 raise ValueError("input to from_unicode() must be a unicode string")
543 if not (origin
is None or isinstance(origin
, Name
)):
544 raise ValueError("origin must be a Name or None")
554 return Name(['']) # no Unicode "u" on this constant!
573 elif c
== u
'.' or c
== u
'\u3002' or \
574 c
== u
'\uff0e' or c
== u
'\uff61':
577 labels
.append(encodings
.idna
.ToASCII(label
))
588 labels
.append(encodings
.idna
.ToASCII(label
))
591 if (len(labels
) == 0 or labels
[-1] != '') and not origin
is None:
592 labels
.extend(list(origin
.labels
))
595 def from_text(text
, origin
= root
):
596 """Convert text into a Name object.
597 @rtype: dns.name.Name object
600 if not isinstance(text
, str):
601 if isinstance(text
, unicode) and sys
.hexversion
>= 0x02030000:
602 return from_unicode(text
, origin
)
604 raise ValueError("input to from_text() must be a string")
605 if not (origin
is None or isinstance(origin
, Name
)):
606 raise ValueError("origin must be a Name or None")
652 if (len(labels
) == 0 or labels
[-1] != '') and not origin
is None:
653 labels
.extend(list(origin
.labels
))
656 def from_wire(message
, current
):
657 """Convert possibly compressed wire format into a Name.
658 @param message: the entire DNS message
659 @type message: string
660 @param current: the offset of the beginning of the name from the start
663 @raises dns.name.BadPointer: a compression pointer did not point backwards
665 @raises dns.name.BadLabelType: an invalid label type was encountered.
666 @returns: a tuple consisting of the name that was read and the number
667 of bytes of the wire format message which were consumed reading it
668 @rtype: (dns.name.Name object, int) tuple
671 if not isinstance(message
, str):
672 raise ValueError("input to from_wire() must be a byte string")
674 biggest_pointer
= current
676 count
= ord(message
[current
])
681 labels
.append(message
[current
: current
+ count
])
686 current
= (count
& 0x3f) * 256 + ord(message
[current
])
689 if current
>= biggest_pointer
:
691 biggest_pointer
= current
695 count
= ord(message
[current
])
700 return (Name(labels
), cused
)