Package dns :: Module name
[hide private]
[frames] | no frames]

Source Code for Module dns.name

  1  # Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. 
  2  # 
  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. 
  7  # 
  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. 
 15   
 16  """DNS Names. 
 17   
 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 
 22  """ 
 23   
 24  import cStringIO 
 25  import struct 
 26  import sys 
 27  import copy 
 28   
 29  if sys.hexversion >= 0x02030000: 
 30      import encodings.idna 
 31   
 32  import dns.exception 
 33  import dns.wiredata 
 34   
 35  NAMERELN_NONE = 0 
 36  NAMERELN_SUPERDOMAIN = 1 
 37  NAMERELN_SUBDOMAIN = 2 
 38  NAMERELN_EQUAL = 3 
 39  NAMERELN_COMMONANCESTOR = 4 
 40   
41 -class EmptyLabel(dns.exception.SyntaxError):
42 """A DNS label is empty."""
43
44 -class BadEscape(dns.exception.SyntaxError):
45 """An escaped code in a text format of DNS name is invalid."""
46
47 -class BadPointer(dns.exception.FormError):
48 """A DNS compression pointer points forward instead of backward."""
49
50 -class BadLabelType(dns.exception.FormError):
51 """The label type in DNS name wire format is unknown."""
52
53 -class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):
54 """An attempt was made to convert a non-absolute name to 55 wire when there was also a non-absolute (or missing) origin."""
56
57 -class NameTooLong(dns.exception.FormError):
58 """A DNS name is > 255 octets long."""
59
60 -class LabelTooLong(dns.exception.SyntaxError):
61 """A DNS label is > 63 octets long."""
62
63 -class AbsoluteConcatenation(dns.exception.DNSException):
64 """An attempt was made to append anything other than the 65 empty name to an absolute DNS name."""
66
67 -class NoParent(dns.exception.DNSException):
68 """An attempt was made to get the parent of the root name 69 or the empty name."""
70 71 _escaped = { 72 '"' : True, 73 '(' : True, 74 ')' : True, 75 '.' : True, 76 ';' : True, 77 '\\' : True, 78 '@' : True, 79 '$' : True 80 } 81
82 -def _escapify(label, unicode_mode=False):
83 """Escape the characters in label which need it. 84 @param unicode_mode: escapify only special and whitespace (<= 0x20) 85 characters 86 @returns: the escaped string 87 @rtype: string""" 88 text = '' 89 for c in label: 90 if c in _escaped: 91 text += '\\' + c 92 elif ord(c) > 0x20 and ord(c) < 0x7F: 93 text += c 94 else: 95 if unicode_mode and ord(c) >= 0x7F: 96 text += c 97 else: 98 text += '\\%03d' % ord(c) 99 return text
100
101 -def _validate_labels(labels):
102 """Check for empty labels in the middle of a label sequence, 103 labels that are too long, and for too many labels. 104 @raises NameTooLong: the name as a whole is too long 105 @raises LabelTooLong: an individual label is too long 106 @raises EmptyLabel: a label is empty (i.e. the root label) and appears 107 in a position other than the end of the label sequence""" 108 109 l = len(labels) 110 total = 0 111 i = -1 112 j = 0 113 for label in labels: 114 ll = len(label) 115 total += ll + 1 116 if ll > 63: 117 raise LabelTooLong 118 if i < 0 and label == '': 119 i = j 120 j += 1 121 if total > 255: 122 raise NameTooLong 123 if i >= 0 and i != l - 1: 124 raise EmptyLabel
125
126 -class Name(object):
127 """A DNS name. 128 129 The dns.name.Name class represents a DNS name as a tuple of labels. 130 Instances of the class are immutable. 131 132 @ivar labels: The tuple of labels in the name. Each label is a string of 133 up to 63 octets.""" 134 135 __slots__ = ['labels'] 136
137 - def __init__(self, labels):
138 """Initialize a domain name from a list of labels. 139 @param labels: the labels 140 @type labels: any iterable whose values are strings 141 """ 142 143 super(Name, self).__setattr__('labels', tuple(labels)) 144 _validate_labels(self.labels)
145
146 - def __setattr__(self, name, value):
147 raise TypeError("object doesn't support attribute assignment")
148
149 - def __copy__(self):
150 return Name(self.labels)
151
152 - def __deepcopy__(self, memo):
153 return Name(copy.deepcopy(self.labels, memo))
154
155 - def __getstate__(self):
156 return { 'labels' : self.labels }
157
158 - def __setstate__(self, state):
159 super(Name, self).__setattr__('labels', state['labels']) 160 _validate_labels(self.labels)
161
162 - def is_absolute(self):
163 """Is the most significant label of this name the root label? 164 @rtype: bool 165 """ 166 167 return len(self.labels) > 0 and self.labels[-1] == ''
168
169 - def is_wild(self):
170 """Is this name wild? (I.e. Is the least significant label '*'?) 171 @rtype: bool 172 """ 173 174 return len(self.labels) > 0 and self.labels[0] == '*'
175
176 - def __hash__(self):
177 """Return a case-insensitive hash of the name. 178 @rtype: int 179 """ 180 181 h = 0L 182 for label in self.labels: 183 for c in label: 184 h += ( h << 3 ) + ord(c.lower()) 185 return int(h % sys.maxint)
186
187 - def fullcompare(self, other):
188 """Compare two names, returning a 3-tuple (relation, order, nlabels). 189 190 I{relation} describes the relation ship beween the names, 191 and is one of: dns.name.NAMERELN_NONE, 192 dns.name.NAMERELN_SUPERDOMAIN, dns.name.NAMERELN_SUBDOMAIN, 193 dns.name.NAMERELN_EQUAL, or dns.name.NAMERELN_COMMONANCESTOR 194 195 I{order} is < 0 if self < other, > 0 if self > other, and == 196 0 if self == other. A relative name is always less than an 197 absolute name. If both names have the same relativity, then 198 the DNSSEC order relation is used to order them. 199 200 I{nlabels} is the number of significant labels that the two names 201 have in common. 202 """ 203 204 sabs = self.is_absolute() 205 oabs = other.is_absolute() 206 if sabs != oabs: 207 if sabs: 208 return (NAMERELN_NONE, 1, 0) 209 else: 210 return (NAMERELN_NONE, -1, 0) 211 l1 = len(self.labels) 212 l2 = len(other.labels) 213 ldiff = l1 - l2 214 if ldiff < 0: 215 l = l1 216 else: 217 l = l2 218 219 order = 0 220 nlabels = 0 221 namereln = NAMERELN_NONE 222 while l > 0: 223 l -= 1 224 l1 -= 1 225 l2 -= 1 226 label1 = self.labels[l1].lower() 227 label2 = other.labels[l2].lower() 228 if label1 < label2: 229 order = -1 230 if nlabels > 0: 231 namereln = NAMERELN_COMMONANCESTOR 232 return (namereln, order, nlabels) 233 elif label1 > label2: 234 order = 1 235 if nlabels > 0: 236 namereln = NAMERELN_COMMONANCESTOR 237 return (namereln, order, nlabels) 238 nlabels += 1 239 order = ldiff 240 if ldiff < 0: 241 namereln = NAMERELN_SUPERDOMAIN 242 elif ldiff > 0: 243 namereln = NAMERELN_SUBDOMAIN 244 else: 245 namereln = NAMERELN_EQUAL 246 return (namereln, order, nlabels)
247
248 - def is_subdomain(self, other):
249 """Is self a subdomain of other? 250 251 The notion of subdomain includes equality. 252 @rtype: bool 253 """ 254 255 (nr, o, nl) = self.fullcompare(other) 256 if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL: 257 return True 258 return False
259
260 - def is_superdomain(self, other):
261 """Is self a superdomain of other? 262 263 The notion of subdomain includes equality. 264 @rtype: bool 265 """ 266 267 (nr, o, nl) = self.fullcompare(other) 268 if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL: 269 return True 270 return False
271
272 - def canonicalize(self):
273 """Return a name which is equal to the current name, but is in 274 DNSSEC canonical form. 275 @rtype: dns.name.Name object 276 """ 277 278 return Name([x.lower() for x in self.labels])
279
280 - def __eq__(self, other):
281 if isinstance(other, Name): 282 return self.fullcompare(other)[1] == 0 283 else: 284 return False
285
286 - def __ne__(self, other):
287 if isinstance(other, Name): 288 return self.fullcompare(other)[1] != 0 289 else: 290 return True
291
292 - def __lt__(self, other):
293 if isinstance(other, Name): 294 return self.fullcompare(other)[1] < 0 295 else: 296 return NotImplemented
297
298 - def __le__(self, other):
299 if isinstance(other, Name): 300 return self.fullcompare(other)[1] <= 0 301 else: 302 return NotImplemented
303
304 - def __ge__(self, other):
305 if isinstance(other, Name): 306 return self.fullcompare(other)[1] >= 0 307 else: 308 return NotImplemented
309
310 - def __gt__(self, other):
311 if isinstance(other, Name): 312 return self.fullcompare(other)[1] > 0 313 else: 314 return NotImplemented
315
316 - def __repr__(self):
317 return '<DNS name ' + self.__str__() + '>'
318
319 - def __str__(self):
320 return self.to_text(False)
321
322 - def to_text(self, omit_final_dot = False):
323 """Convert name to text format. 324 @param omit_final_dot: If True, don't emit the final dot (denoting the 325 root label) for absolute names. The default is False. 326 @rtype: string 327 """ 328 329 if len(self.labels) == 0: 330 return '@' 331 if len(self.labels) == 1 and self.labels[0] == '': 332 return '.' 333 if omit_final_dot and self.is_absolute(): 334 l = self.labels[:-1] 335 else: 336 l = self.labels 337 s = '.'.join(map(_escapify, l)) 338 return s
339
340 - def to_unicode(self, omit_final_dot = False):
341 """Convert name to Unicode text format. 342 343 IDN ACE lables are converted to Unicode. 344 345 @param omit_final_dot: If True, don't emit the final dot (denoting the 346 root label) for absolute names. The default is False. 347 @rtype: string 348 """ 349 350 if len(self.labels) == 0: 351 return u'@' 352 if len(self.labels) == 1 and self.labels[0] == '': 353 return u'.' 354 if omit_final_dot and self.is_absolute(): 355 l = self.labels[:-1] 356 else: 357 l = self.labels 358 s = u'.'.join([_escapify(encodings.idna.ToUnicode(x), True) for x in l]) 359 return s
360
361 - def to_digestable(self, origin=None):
362 """Convert name to a format suitable for digesting in hashes. 363 364 The name is canonicalized and converted to uncompressed wire format. 365 366 @param origin: If the name is relative and origin is not None, then 367 origin will be appended to it. 368 @type origin: dns.name.Name object 369 @raises NeedAbsoluteNameOrOrigin: All names in wire format are 370 absolute. If self is a relative name, then an origin must be supplied; 371 if it is missing, then this exception is raised 372 @rtype: string 373 """ 374 375 if not self.is_absolute(): 376 if origin is None or not origin.is_absolute(): 377 raise NeedAbsoluteNameOrOrigin 378 labels = list(self.labels) 379 labels.extend(list(origin.labels)) 380 else: 381 labels = self.labels 382 dlabels = ["%s%s" % (chr(len(x)), x.lower()) for x in labels] 383 return ''.join(dlabels)
384
385 - def to_wire(self, file = None, compress = None, origin = None):
386 """Convert name to wire format, possibly compressing it. 387 388 @param file: the file where the name is emitted (typically 389 a cStringIO file). If None, a string containing the wire name 390 will be returned. 391 @type file: file or None 392 @param compress: The compression table. If None (the default) names 393 will not be compressed. 394 @type compress: dict 395 @param origin: If the name is relative and origin is not None, then 396 origin will be appended to it. 397 @type origin: dns.name.Name object 398 @raises NeedAbsoluteNameOrOrigin: All names in wire format are 399 absolute. If self is a relative name, then an origin must be supplied; 400 if it is missing, then this exception is raised 401 """ 402 403 if file is None: 404 file = cStringIO.StringIO() 405 want_return = True 406 else: 407 want_return = False 408 409 if not self.is_absolute(): 410 if origin is None or not origin.is_absolute(): 411 raise NeedAbsoluteNameOrOrigin 412 labels = list(self.labels) 413 labels.extend(list(origin.labels)) 414 else: 415 labels = self.labels 416 i = 0 417 for label in labels: 418 n = Name(labels[i:]) 419 i += 1 420 if not compress is None: 421 pos = compress.get(n) 422 else: 423 pos = None 424 if not pos is None: 425 value = 0xc000 + pos 426 s = struct.pack('!H', value) 427 file.write(s) 428 break 429 else: 430 if not compress is None and len(n) > 1: 431 pos = file.tell() 432 if pos <= 0x3fff: 433 compress[n] = pos 434 l = len(label) 435 file.write(chr(l)) 436 if l > 0: 437 file.write(label) 438 if want_return: 439 return file.getvalue()
440
441 - def __len__(self):
442 """The length of the name (in labels). 443 @rtype: int 444 """ 445 446 return len(self.labels)
447
448 - def __getitem__(self, index):
449 return self.labels[index]
450
451 - def __getslice__(self, start, stop):
452 return self.labels[start:stop]
453
454 - def __add__(self, other):
455 return self.concatenate(other)
456
457 - def __sub__(self, other):
458 return self.relativize(other)
459
460 - def split(self, depth):
461 """Split a name into a prefix and suffix at depth. 462 463 @param depth: the number of labels in the suffix 464 @type depth: int 465 @raises ValueError: the depth was not >= 0 and <= the length of the 466 name. 467 @returns: the tuple (prefix, suffix) 468 @rtype: tuple 469 """ 470 471 l = len(self.labels) 472 if depth == 0: 473 return (self, dns.name.empty) 474 elif depth == l: 475 return (dns.name.empty, self) 476 elif depth < 0 or depth > l: 477 raise ValueError('depth must be >= 0 and <= the length of the name') 478 return (Name(self[: -depth]), Name(self[-depth :]))
479
480 - def concatenate(self, other):
481 """Return a new name which is the concatenation of self and other. 482 @rtype: dns.name.Name object 483 @raises AbsoluteConcatenation: self is absolute and other is 484 not the empty name 485 """ 486 487 if self.is_absolute() and len(other) > 0: 488 raise AbsoluteConcatenation 489 labels = list(self.labels) 490 labels.extend(list(other.labels)) 491 return Name(labels)
492
493 - def relativize(self, origin):
494 """If self is a subdomain of origin, return a new name which is self 495 relative to origin. Otherwise return self. 496 @rtype: dns.name.Name object 497 """ 498 499 if not origin is None and self.is_subdomain(origin): 500 return Name(self[: -len(origin)]) 501 else: 502 return self
503
504 - def derelativize(self, origin):
505 """If self is a relative name, return a new name which is the 506 concatenation of self and origin. Otherwise return self. 507 @rtype: dns.name.Name object 508 """ 509 510 if not self.is_absolute(): 511 return self.concatenate(origin) 512 else: 513 return self
514
515 - def choose_relativity(self, origin=None, relativize=True):
516 """Return a name with the relativity desired by the caller. If 517 origin is None, then self is returned. Otherwise, if 518 relativize is true the name is relativized, and if relativize is 519 false the name is derelativized. 520 @rtype: dns.name.Name object 521 """ 522 523 if origin: 524 if relativize: 525 return self.relativize(origin) 526 else: 527 return self.derelativize(origin) 528 else: 529 return self
530
531 - def parent(self):
532 """Return the parent of the name. 533 @rtype: dns.name.Name object 534 @raises NoParent: the name is either the root name or the empty name, 535 and thus has no parent. 536 """ 537 if self == root or self == empty: 538 raise NoParent 539 return Name(self.labels[1:])
540 541 root = Name(['']) 542 empty = Name([]) 543
544 -def from_unicode(text, origin = root):
545 """Convert unicode text into a Name object. 546 547 Lables are encoded in IDN ACE form. 548 549 @rtype: dns.name.Name object 550 """ 551 552 if not isinstance(text, unicode): 553 raise ValueError("input to from_unicode() must be a unicode string") 554 if not (origin is None or isinstance(origin, Name)): 555 raise ValueError("origin must be a Name or None") 556 labels = [] 557 label = u'' 558 escaping = False 559 edigits = 0 560 total = 0 561 if text == u'@': 562 text = u'' 563 if text: 564 if text == u'.': 565 return Name(['']) # no Unicode "u" on this constant! 566 for c in text: 567 if escaping: 568 if edigits == 0: 569 if c.isdigit(): 570 total = int(c) 571 edigits += 1 572 else: 573 label += c 574 escaping = False 575 else: 576 if not c.isdigit(): 577 raise BadEscape 578 total *= 10 579 total += int(c) 580 edigits += 1 581 if edigits == 3: 582 escaping = False 583 label += chr(total) 584 elif c == u'.' or c == u'\u3002' or \ 585 c == u'\uff0e' or c == u'\uff61': 586 if len(label) == 0: 587 raise EmptyLabel 588 if len(label) > 63: 589 # otherwise encodings.idna raises UnicodeError later 590 raise LabelTooLong 591 labels.append(encodings.idna.ToASCII(label)) 592 label = u'' 593 elif c == u'\\': 594 escaping = True 595 edigits = 0 596 total = 0 597 else: 598 label += c 599 if escaping: 600 raise BadEscape 601 if len(label) > 63: 602 # otherwise encodings.idna raises UnicodeError later 603 raise LabelTooLong 604 if len(label) > 0: 605 labels.append(encodings.idna.ToASCII(label)) 606 else: 607 labels.append('') 608 if (len(labels) == 0 or labels[-1] != '') and not origin is None: 609 labels.extend(list(origin.labels)) 610 return Name(labels)
611
612 -def from_text(text, origin = root):
613 """Convert text into a Name object. 614 @rtype: dns.name.Name object 615 """ 616 617 if not isinstance(text, str): 618 if isinstance(text, unicode) and sys.hexversion >= 0x02030000: 619 return from_unicode(text, origin) 620 else: 621 raise ValueError("input to from_text() must be a string") 622 if not (origin is None or isinstance(origin, Name)): 623 raise ValueError("origin must be a Name or None") 624 labels = [] 625 label = '' 626 escaping = False 627 edigits = 0 628 total = 0 629 if text == '@': 630 text = '' 631 if text: 632 if text == '.': 633 return Name(['']) 634 for c in text: 635 if escaping: 636 if edigits == 0: 637 if c.isdigit(): 638 total = int(c) 639 edigits += 1 640 else: 641 label += c 642 escaping = False 643 else: 644 if not c.isdigit(): 645 raise BadEscape 646 total *= 10 647 total += int(c) 648 edigits += 1 649 if edigits == 3: 650 escaping = False 651 label += chr(total) 652 elif c == '.': 653 if len(label) == 0: 654 raise EmptyLabel 655 labels.append(label) 656 label = '' 657 elif c == '\\': 658 escaping = True 659 edigits = 0 660 total = 0 661 else: 662 label += c 663 if escaping: 664 raise BadEscape 665 if len(label) > 0: 666 labels.append(label) 667 else: 668 labels.append('') 669 if (len(labels) == 0 or labels[-1] != '') and not origin is None: 670 labels.extend(list(origin.labels)) 671 return Name(labels)
672
673 -def from_wire(message, current):
674 """Convert possibly compressed wire format into a Name. 675 @param message: the entire DNS message 676 @type message: string 677 @param current: the offset of the beginning of the name from the start 678 of the message 679 @type current: int 680 @raises dns.name.BadPointer: a compression pointer did not point backwards 681 in the message 682 @raises dns.name.BadLabelType: an invalid label type was encountered. 683 @returns: a tuple consisting of the name that was read and the number 684 of bytes of the wire format message which were consumed reading it 685 @rtype: (dns.name.Name object, int) tuple 686 """ 687 688 if not isinstance(message, str): 689 raise ValueError("input to from_wire() must be a byte string") 690 message = dns.wiredata.maybe_wrap(message) 691 labels = [] 692 biggest_pointer = current 693 hops = 0 694 count = ord(message[current]) 695 current += 1 696 cused = 1 697 while count != 0: 698 if count < 64: 699 labels.append(message[current : current + count].unwrap()) 700 current += count 701 if hops == 0: 702 cused += count 703 elif count >= 192: 704 current = (count & 0x3f) * 256 + ord(message[current]) 705 if hops == 0: 706 cused += 1 707 if current >= biggest_pointer: 708 raise BadPointer 709 biggest_pointer = current 710 hops += 1 711 else: 712 raise BadLabelType 713 count = ord(message[current]) 714 current += 1 715 if hops == 0: 716 cused += 1 717 labels.append('') 718 return (Name(labels), cused)
719