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