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