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

Source Code for Module dns.name

  1  # Copyright (C) 2001-2007, 2009 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   
 28  if sys.hexversion >= 0x02030000: 
 29      import encodings.idna 
 30   
 31  import dns.exception 
 32   
 33  NAMERELN_NONE = 0 
 34  NAMERELN_SUPERDOMAIN = 1 
 35  NAMERELN_SUBDOMAIN = 2 
 36  NAMERELN_EQUAL = 3 
 37  NAMERELN_COMMONANCESTOR = 4 
 38   
39 -class EmptyLabel(dns.exception.SyntaxError):
40 """Raised if a label is empty.""" 41 pass
42
43 -class BadEscape(dns.exception.SyntaxError):
44 """Raised if an escaped code in a text format name is invalid.""" 45 pass
46
47 -class BadPointer(dns.exception.FormError):
48 """Raised if a compression pointer points forward instead of backward.""" 49 pass
50
51 -class BadLabelType(dns.exception.FormError):
52 """Raised if the label type of a wire format name is unknown.""" 53 pass
54
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.""" 58 pass
59
60 -class NameTooLong(dns.exception.FormError):
61 """Raised if a name is > 255 octets long.""" 62 pass
63
64 -class LabelTooLong(dns.exception.SyntaxError):
65 """Raised if a label is > 63 octets long.""" 66 pass
67
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.""" 71 pass
72
73 -class NoParent(dns.exception.DNSException):
74 """Raised if an attempt is made to get the parent of the root name 75 or the empty name.""" 76 pass
77 78 _escaped = { 79 '"' : True, 80 '(' : True, 81 ')' : True, 82 '.' : True, 83 ';' : True, 84 '\\' : True, 85 '@' : True, 86 '$' : True 87 } 88
89 -def _escapify(label):
90 """Escape the characters in label which need it. 91 @returns: the escaped string 92 @rtype: string""" 93 text = '' 94 for c in label: 95 if c in _escaped: 96 text += '\\' + c 97 elif ord(c) > 0x20 and ord(c) < 0x7F: 98 text += c 99 else: 100 text += '\\%03d' % ord(c) 101 return text
102
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""" 110 111 l = len(labels) 112 total = 0 113 i = -1 114 j = 0 115 for label in labels: 116 ll = len(label) 117 total += ll + 1 118 if ll > 63: 119 raise LabelTooLong 120 if i < 0 and label == '': 121 i = j 122 j += 1 123 if total > 255: 124 raise NameTooLong 125 if i >= 0 and i != l - 1: 126 raise EmptyLabel
127
128 -class Name(object):
129 """A DNS name. 130 131 The dns.name.Name class represents a DNS name as a tuple of labels. 132 Instances of the class are immutable. 133 134 @ivar labels: The tuple of labels in the name. Each label is a string of 135 up to 63 octets.""" 136 137 __slots__ = ['labels'] 138
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 143 """ 144 145 super(Name, self).__setattr__('labels', tuple(labels)) 146 _validate_labels(self.labels)
147
148 - def __setattr__(self, name, value):
149 raise TypeError, "object doesn't support attribute assignment"
150
151 - def is_absolute(self):
152 """Is the most significant label of this name the root label? 153 @rtype: bool 154 """ 155 156 return len(self.labels) > 0 and self.labels[-1] == ''
157
158 - def is_wild(self):
159 """Is this name wild? (I.e. Is the least significant label '*'?) 160 @rtype: bool 161 """ 162 163 return len(self.labels) > 0 and self.labels[0] == '*'
164
165 - def __hash__(self):
166 """Return a case-insensitive hash of the name. 167 @rtype: int 168 """ 169 170 h = 0L 171 for label in self.labels: 172 for c in label: 173 h += ( h << 3 ) + ord(c.lower()) 174 return int(h % sys.maxint)
175
176 - def fullcompare(self, other):
177 """Compare two names, returning a 3-tuple (relation, order, nlabels). 178 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 183 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. 188 189 I{nlabels} is the number of significant labels that the two names 190 have in common. 191 """ 192 193 sabs = self.is_absolute() 194 oabs = other.is_absolute() 195 if sabs != oabs: 196 if sabs: 197 return (NAMERELN_NONE, 1, 0) 198 else: 199 return (NAMERELN_NONE, -1, 0) 200 l1 = len(self.labels) 201 l2 = len(other.labels) 202 ldiff = l1 - l2 203 if ldiff < 0: 204 l = l1 205 else: 206 l = l2 207 208 order = 0 209 nlabels = 0 210 namereln = NAMERELN_NONE 211 while l > 0: 212 l -= 1 213 l1 -= 1 214 l2 -= 1 215 label1 = self.labels[l1].lower() 216 label2 = other.labels[l2].lower() 217 if label1 < label2: 218 order = -1 219 if nlabels > 0: 220 namereln = NAMERELN_COMMONANCESTOR 221 return (namereln, order, nlabels) 222 elif label1 > label2: 223 order = 1 224 if nlabels > 0: 225 namereln = NAMERELN_COMMONANCESTOR 226 return (namereln, order, nlabels) 227 nlabels += 1 228 order = ldiff 229 if ldiff < 0: 230 namereln = NAMERELN_SUPERDOMAIN 231 elif ldiff > 0: 232 namereln = NAMERELN_SUBDOMAIN 233 else: 234 namereln = NAMERELN_EQUAL 235 return (namereln, order, nlabels)
236
237 - def is_subdomain(self, other):
238 """Is self a subdomain of other? 239 240 The notion of subdomain includes equality. 241 @rtype: bool 242 """ 243 244 (nr, o, nl) = self.fullcompare(other) 245 if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL: 246 return True 247 return False
248
249 - def is_superdomain(self, other):
250 """Is self a superdomain of other? 251 252 The notion of subdomain includes equality. 253 @rtype: bool 254 """ 255 256 (nr, o, nl) = self.fullcompare(other) 257 if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL: 258 return True 259 return False
260
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 265 """ 266 267 return Name([x.lower() for x in self.labels])
268
269 - def __eq__(self, other):
270 if isinstance(other, Name): 271 return self.fullcompare(other)[1] == 0 272 else: 273 return False
274
275 - def __ne__(self, other):
276 if isinstance(other, Name): 277 return self.fullcompare(other)[1] != 0 278 else: 279 return True
280
281 - def __lt__(self, other):
282 if isinstance(other, Name): 283 return self.fullcompare(other)[1] < 0 284 else: 285 return NotImplemented
286
287 - def __le__(self, other):
288 if isinstance(other, Name): 289 return self.fullcompare(other)[1] <= 0 290 else: 291 return NotImplemented
292
293 - def __ge__(self, other):
294 if isinstance(other, Name): 295 return self.fullcompare(other)[1] >= 0 296 else: 297 return NotImplemented
298
299 - def __gt__(self, other):
300 if isinstance(other, Name): 301 return self.fullcompare(other)[1] > 0 302 else: 303 return NotImplemented
304
305 - def __repr__(self):
306 return '<DNS name ' + self.__str__() + '>'
307
308 - def __str__(self):
309 return self.to_text(False)
310
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. 315 @rtype: string 316 """ 317 318 if len(self.labels) == 0: 319 return '@' 320 if len(self.labels) == 1 and self.labels[0] == '': 321 return '.' 322 if omit_final_dot and self.is_absolute(): 323 l = self.labels[:-1] 324 else: 325 l = self.labels 326 s = '.'.join(map(_escapify, l)) 327 return s
328
329 - def to_unicode(self, omit_final_dot = False):
330 """Convert name to Unicode text format. 331 332 IDN ACE lables are converted to Unicode. 333 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. 336 @rtype: string 337 """ 338 339 if len(self.labels) == 0: 340 return u'@' 341 if len(self.labels) == 1 and self.labels[0] == '': 342 return u'.' 343 if omit_final_dot and self.is_absolute(): 344 l = self.labels[:-1] 345 else: 346 l = self.labels 347 s = u'.'.join([encodings.idna.ToUnicode(_escapify(x)) for x in l]) 348 return s
349
350 - def to_digestable(self, origin=None):
351 """Convert name to a format suitable for digesting in hashes. 352 353 The name is canonicalized and converted to uncompressed wire format. 354 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 361 @rtype: string 362 """ 363 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)) 369 else: 370 labels = self.labels 371 dlabels = ["%s%s" % (chr(len(x)), x.lower()) for x in labels] 372 return ''.join(dlabels)
373
374 - def to_wire(self, file = None, compress = None, origin = None):
375 """Convert name to wire format, possibly compressing it. 376 377 @param file: the file where the name is emitted (typically 378 a cStringIO file). If None, a string containing the wire name 379 will be returned. 380 @type file: file or None 381 @param compress: The compression table. If None (the default) names 382 will not be compressed. 383 @type compress: dict 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 390 """ 391 392 if file is None: 393 file = cStringIO.StringIO() 394 want_return = True 395 else: 396 want_return = False 397 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)) 403 else: 404 labels = self.labels 405 i = 0 406 for label in labels: 407 n = Name(labels[i:]) 408 i += 1 409 if not compress is None: 410 pos = compress.get(n) 411 else: 412 pos = None 413 if not pos is None: 414 value = 0xc000 + pos 415 s = struct.pack('!H', value) 416 file.write(s) 417 break 418 else: 419 if not compress is None and len(n) > 1: 420 pos = file.tell() 421 if pos < 0xc000: 422 compress[n] = pos 423 l = len(label) 424 file.write(chr(l)) 425 if l > 0: 426 file.write(label) 427 if want_return: 428 return file.getvalue()
429
430 - def __len__(self):
431 """The length of the name (in labels). 432 @rtype: int 433 """ 434 435 return len(self.labels)
436
437 - def __getitem__(self, index):
438 return self.labels[index]
439
440 - def __getslice__(self, start, stop):
441 return self.labels[start:stop]
442
443 - def __add__(self, other):
444 return self.concatenate(other)
445
446 - def __sub__(self, other):
447 return self.relativize(other)
448
449 - def split(self, depth):
450 """Split a name into a prefix and suffix at depth. 451 452 @param depth: the number of labels in the suffix 453 @type depth: int 454 @raises ValueError: the depth was not >= 0 and <= the length of the 455 name. 456 @returns: the tuple (prefix, suffix) 457 @rtype: tuple 458 """ 459 460 l = len(self.labels) 461 if depth == 0: 462 return (self, dns.name.empty) 463 elif depth == l: 464 return (dns.name.empty, self) 465 elif depth < 0 or depth > l: 466 raise ValueError, \ 467 'depth must be >= 0 and <= the length of the name' 468 return (Name(self[: -depth]), Name(self[-depth :]))
469
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 474 not the empty name 475 """ 476 477 if self.is_absolute() and len(other) > 0: 478 raise AbsoluteConcatenation 479 labels = list(self.labels) 480 labels.extend(list(other.labels)) 481 return Name(labels)
482
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 487 """ 488 489 if not origin is None and self.is_subdomain(origin): 490 return Name(self[: -len(origin)]) 491 else: 492 return self
493
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 498 """ 499 500 if not self.is_absolute(): 501 return self.concatenate(origin) 502 else: 503 return self
504
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 511 """ 512 513 if origin: 514 if relativize: 515 return self.relativize(origin) 516 else: 517 return self.derelativize(origin) 518 else: 519 return self
520
521 - def parent(self):
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. 526 """ 527 if self == root or self == empty: 528 raise NoParent 529 return Name(self.labels[1:])
530 531 root = Name(['']) 532 empty = Name([]) 533
534 -def from_unicode(text, origin = root):
535 """Convert unicode text into a Name object. 536 537 Lables are encoded in IDN ACE form. 538 539 @rtype: dns.name.Name object 540 """ 541 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" 546 labels = [] 547 label = u'' 548 escaping = False 549 edigits = 0 550 total = 0 551 if text == u'@': 552 text = u'' 553 if text: 554 if text == u'.': 555 return Name(['']) # no Unicode "u" on this constant! 556 for c in text: 557 if escaping: 558 if edigits == 0: 559 if c.isdigit(): 560 total = int(c) 561 edigits += 1 562 else: 563 label += c 564 escaping = False 565 else: 566 if not c.isdigit(): 567 raise BadEscape 568 total *= 10 569 total += int(c) 570 edigits += 1 571 if edigits == 3: 572 escaping = False 573 label += chr(total) 574 elif c == u'.' or c == u'\u3002' or \ 575 c == u'\uff0e' or c == u'\uff61': 576 if len(label) == 0: 577 raise EmptyLabel 578 labels.append(encodings.idna.ToASCII(label)) 579 label = u'' 580 elif c == u'\\': 581 escaping = True 582 edigits = 0 583 total = 0 584 else: 585 label += c 586 if escaping: 587 raise BadEscape 588 if len(label) > 0: 589 labels.append(encodings.idna.ToASCII(label)) 590 else: 591 labels.append('') 592 if (len(labels) == 0 or labels[-1] != '') and not origin is None: 593 labels.extend(list(origin.labels)) 594 return Name(labels)
595
596 -def from_text(text, origin = root):
597 """Convert text into a Name object. 598 @rtype: dns.name.Name object 599 """ 600 601 if not isinstance(text, str): 602 if isinstance(text, unicode) and sys.hexversion >= 0x02030000: 603 return from_unicode(text, origin) 604 else: 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" 608 labels = [] 609 label = '' 610 escaping = False 611 edigits = 0 612 total = 0 613 if text == '@': 614 text = '' 615 if text: 616 if text == '.': 617 return Name(['']) 618 for c in text: 619 if escaping: 620 if edigits == 0: 621 if c.isdigit(): 622 total = int(c) 623 edigits += 1 624 else: 625 label += c 626 escaping = False 627 else: 628 if not c.isdigit(): 629 raise BadEscape 630 total *= 10 631 total += int(c) 632 edigits += 1 633 if edigits == 3: 634 escaping = False 635 label += chr(total) 636 elif c == '.': 637 if len(label) == 0: 638 raise EmptyLabel 639 labels.append(label) 640 label = '' 641 elif c == '\\': 642 escaping = True 643 edigits = 0 644 total = 0 645 else: 646 label += c 647 if escaping: 648 raise BadEscape 649 if len(label) > 0: 650 labels.append(label) 651 else: 652 labels.append('') 653 if (len(labels) == 0 or labels[-1] != '') and not origin is None: 654 labels.extend(list(origin.labels)) 655 return Name(labels)
656
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 662 of the message 663 @type current: int 664 @raises dns.name.BadPointer: a compression pointer did not point backwards 665 in the message 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 670 """ 671 672 if not isinstance(message, str): 673 raise ValueError, "input to from_wire() must be a byte string" 674 labels = [] 675 biggest_pointer = current 676 hops = 0 677 count = ord(message[current]) 678 current += 1 679 cused = 1 680 while count != 0: 681 if count < 64: 682 labels.append(message[current : current + count]) 683 current += count 684 if hops == 0: 685 cused += count 686 elif count >= 192: 687 current = (count & 0x3f) * 256 + ord(message[current]) 688 if hops == 0: 689 cused += 1 690 if current >= biggest_pointer: 691 raise BadPointer 692 biggest_pointer = current 693 hops += 1 694 else: 695 raise BadLabelType 696 count = ord(message[current]) 697 current += 1 698 if hops == 0: 699 cused += 1 700 labels.append('') 701 return (Name(labels), cused)
702