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

Source Code for Module dns.name

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