| 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 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('depth must be >= 0 and <= the length of the name')
|
|---|
| 467 | return (Name(self[: -depth]), Name(self[-depth :]))
|
|---|
| 468 |
|
|---|
| 469 | def concatenate(self, other):
|
|---|
| 470 | """Return a new name which is the concatenation of self and other.
|
|---|
| 471 | @rtype: dns.name.Name object
|
|---|
| 472 | @raises AbsoluteConcatenation: self is absolute and other is
|
|---|
| 473 | not the empty name
|
|---|
| 474 | """
|
|---|
| 475 |
|
|---|
| 476 | if self.is_absolute() and len(other) > 0:
|
|---|
| 477 | raise AbsoluteConcatenation
|
|---|
| 478 | labels = list(self.labels)
|
|---|
| 479 | labels.extend(list(other.labels))
|
|---|
| 480 | return Name(labels)
|
|---|
| 481 |
|
|---|
| 482 | def relativize(self, origin):
|
|---|
| 483 | """If self is a subdomain of origin, return a new name which is self
|
|---|
| 484 | relative to origin. Otherwise return self.
|
|---|
| 485 | @rtype: dns.name.Name object
|
|---|
| 486 | """
|
|---|
| 487 |
|
|---|
| 488 | if not origin is None and self.is_subdomain(origin):
|
|---|
| 489 | return Name(self[: -len(origin)])
|
|---|
| 490 | else:
|
|---|
| 491 | return self
|
|---|
| 492 |
|
|---|
| 493 | def derelativize(self, origin):
|
|---|
| 494 | """If self is a relative name, return a new name which is the
|
|---|
| 495 | concatenation of self and origin. Otherwise return self.
|
|---|
| 496 | @rtype: dns.name.Name object
|
|---|
| 497 | """
|
|---|
| 498 |
|
|---|
| 499 | if not self.is_absolute():
|
|---|
| 500 | return self.concatenate(origin)
|
|---|
| 501 | else:
|
|---|
| 502 | return self
|
|---|
| 503 |
|
|---|
| 504 | def choose_relativity(self, origin=None, relativize=True):
|
|---|
| 505 | """Return a name with the relativity desired by the caller. If
|
|---|
| 506 | origin is None, then self is returned. Otherwise, if
|
|---|
| 507 | relativize is true the name is relativized, and if relativize is
|
|---|
| 508 | false the name is derelativized.
|
|---|
| 509 | @rtype: dns.name.Name object
|
|---|
| 510 | """
|
|---|
| 511 |
|
|---|
| 512 | if origin:
|
|---|
| 513 | if relativize:
|
|---|
| 514 | return self.relativize(origin)
|
|---|
| 515 | else:
|
|---|
| 516 | return self.derelativize(origin)
|
|---|
| 517 | else:
|
|---|
| 518 | return self
|
|---|
| 519 |
|
|---|
| 520 | def parent(self):
|
|---|
| 521 | """Return the parent of the name.
|
|---|
| 522 | @rtype: dns.name.Name object
|
|---|
| 523 | @raises NoParent: the name is either the root name or the empty name,
|
|---|
| 524 | and thus has no parent.
|
|---|
| 525 | """
|
|---|
| 526 | if self == root or self == empty:
|
|---|
| 527 | raise NoParent
|
|---|
| 528 | return Name(self.labels[1:])
|
|---|
| 529 |
|
|---|
| 530 | root = Name([''])
|
|---|
| 531 | empty = Name([])
|
|---|
| 532 |
|
|---|
| 533 | def from_unicode(text, origin = root):
|
|---|
| 534 | """Convert unicode text into a Name object.
|
|---|
| 535 |
|
|---|
| 536 | Lables are encoded in IDN ACE form.
|
|---|
| 537 |
|
|---|
| 538 | @rtype: dns.name.Name object
|
|---|
| 539 | """
|
|---|
| 540 |
|
|---|
| 541 | if not isinstance(text, unicode):
|
|---|
| 542 | raise ValueError("input to from_unicode() must be a unicode string")
|
|---|
| 543 | if not (origin is None or isinstance(origin, Name)):
|
|---|
| 544 | raise ValueError("origin must be a Name or None")
|
|---|
| 545 | labels = []
|
|---|
| 546 | label = u''
|
|---|
| 547 | escaping = False
|
|---|
| 548 | edigits = 0
|
|---|
| 549 | total = 0
|
|---|
| 550 | if text == u'@':
|
|---|
| 551 | text = u''
|
|---|
| 552 | if text:
|
|---|
| 553 | if text == u'.':
|
|---|
| 554 | return Name(['']) # no Unicode "u" on this constant!
|
|---|
| 555 | for c in text:
|
|---|
| 556 | if escaping:
|
|---|
| 557 | if edigits == 0:
|
|---|
| 558 | if c.isdigit():
|
|---|
| 559 | total = int(c)
|
|---|
| 560 | edigits += 1
|
|---|
| 561 | else:
|
|---|
| 562 | label += c
|
|---|
| 563 | escaping = False
|
|---|
| 564 | else:
|
|---|
| 565 | if not c.isdigit():
|
|---|
| 566 | raise BadEscape
|
|---|
| 567 | total *= 10
|
|---|
| 568 | total += int(c)
|
|---|
| 569 | edigits += 1
|
|---|
| 570 | if edigits == 3:
|
|---|
| 571 | escaping = False
|
|---|
| 572 | label += chr(total)
|
|---|
| 573 | elif c == u'.' or c == u'\u3002' or \
|
|---|
| 574 | c == u'\uff0e' or c == u'\uff61':
|
|---|
| 575 | if len(label) == 0:
|
|---|
| 576 | raise EmptyLabel
|
|---|
| 577 | labels.append(encodings.idna.ToASCII(label))
|
|---|
| 578 | label = u''
|
|---|
| 579 | elif c == u'\\':
|
|---|
| 580 | escaping = True
|
|---|
| 581 | edigits = 0
|
|---|
| 582 | total = 0
|
|---|
| 583 | else:
|
|---|
| 584 | label += c
|
|---|
| 585 | if escaping:
|
|---|
| 586 | raise BadEscape
|
|---|
| 587 | if len(label) > 0:
|
|---|
| 588 | labels.append(encodings.idna.ToASCII(label))
|
|---|
| 589 | else:
|
|---|
| 590 | labels.append('')
|
|---|
| 591 | if (len(labels) == 0 or labels[-1] != '') and not origin is None:
|
|---|
| 592 | labels.extend(list(origin.labels))
|
|---|
| 593 | return Name(labels)
|
|---|
| 594 |
|
|---|
| 595 | def from_text(text, origin = root):
|
|---|
| 596 | """Convert text into a Name object.
|
|---|
| 597 | @rtype: dns.name.Name object
|
|---|
| 598 | """
|
|---|
| 599 |
|
|---|
| 600 | if not isinstance(text, str):
|
|---|
| 601 | if isinstance(text, unicode) and sys.hexversion >= 0x02030000:
|
|---|
| 602 | return from_unicode(text, origin)
|
|---|
| 603 | else:
|
|---|
| 604 | raise ValueError("input to from_text() must be a string")
|
|---|
| 605 | if not (origin is None or isinstance(origin, Name)):
|
|---|
| 606 | raise ValueError("origin must be a Name or None")
|
|---|
| 607 | labels = []
|
|---|
| 608 | label = ''
|
|---|
| 609 | escaping = False
|
|---|
| 610 | edigits = 0
|
|---|
| 611 | total = 0
|
|---|
| 612 | if text == '@':
|
|---|
| 613 | text = ''
|
|---|
| 614 | if text:
|
|---|
| 615 | if text == '.':
|
|---|
| 616 | return Name([''])
|
|---|
| 617 | for c in text:
|
|---|
| 618 | if escaping:
|
|---|
| 619 | if edigits == 0:
|
|---|
| 620 | if c.isdigit():
|
|---|
| 621 | total = int(c)
|
|---|
| 622 | edigits += 1
|
|---|
| 623 | else:
|
|---|
| 624 | label += c
|
|---|
| 625 | escaping = False
|
|---|
| 626 | else:
|
|---|
| 627 | if not c.isdigit():
|
|---|
| 628 | raise BadEscape
|
|---|
| 629 | total *= 10
|
|---|
| 630 | total += int(c)
|
|---|
| 631 | edigits += 1
|
|---|
| 632 | if edigits == 3:
|
|---|
| 633 | escaping = False
|
|---|
| 634 | label += chr(total)
|
|---|
| 635 | elif c == '.':
|
|---|
| 636 | if len(label) == 0:
|
|---|
| 637 | raise EmptyLabel
|
|---|
| 638 | labels.append(label)
|
|---|
| 639 | label = ''
|
|---|
| 640 | elif c == '\\':
|
|---|
| 641 | escaping = True
|
|---|
| 642 | edigits = 0
|
|---|
| 643 | total = 0
|
|---|
| 644 | else:
|
|---|
| 645 | label += c
|
|---|
| 646 | if escaping:
|
|---|
| 647 | raise BadEscape
|
|---|
| 648 | if len(label) > 0:
|
|---|
| 649 | labels.append(label)
|
|---|
| 650 | else:
|
|---|
| 651 | labels.append('')
|
|---|
| 652 | if (len(labels) == 0 or labels[-1] != '') and not origin is None:
|
|---|
| 653 | labels.extend(list(origin.labels))
|
|---|
| 654 | return Name(labels)
|
|---|
| 655 |
|
|---|
| 656 | def from_wire(message, current):
|
|---|
| 657 | """Convert possibly compressed wire format into a Name.
|
|---|
| 658 | @param message: the entire DNS message
|
|---|
| 659 | @type message: string
|
|---|
| 660 | @param current: the offset of the beginning of the name from the start
|
|---|
| 661 | of the message
|
|---|
| 662 | @type current: int
|
|---|
| 663 | @raises dns.name.BadPointer: a compression pointer did not point backwards
|
|---|
| 664 | in the message
|
|---|
| 665 | @raises dns.name.BadLabelType: an invalid label type was encountered.
|
|---|
| 666 | @returns: a tuple consisting of the name that was read and the number
|
|---|
| 667 | of bytes of the wire format message which were consumed reading it
|
|---|
| 668 | @rtype: (dns.name.Name object, int) tuple
|
|---|
| 669 | """
|
|---|
| 670 |
|
|---|
| 671 | if not isinstance(message, str):
|
|---|
| 672 | raise ValueError("input to from_wire() must be a byte string")
|
|---|
| 673 | labels = []
|
|---|
| 674 | biggest_pointer = current
|
|---|
| 675 | hops = 0
|
|---|
| 676 | count = ord(message[current])
|
|---|
| 677 | current += 1
|
|---|
| 678 | cused = 1
|
|---|
| 679 | while count != 0:
|
|---|
| 680 | if count < 64:
|
|---|
| 681 | labels.append(message[current : current + count])
|
|---|
| 682 | current += count
|
|---|
| 683 | if hops == 0:
|
|---|
| 684 | cused += count
|
|---|
| 685 | elif count >= 192:
|
|---|
| 686 | current = (count & 0x3f) * 256 + ord(message[current])
|
|---|
| 687 | if hops == 0:
|
|---|
| 688 | cused += 1
|
|---|
| 689 | if current >= biggest_pointer:
|
|---|
| 690 | raise BadPointer
|
|---|
| 691 | biggest_pointer = current
|
|---|
| 692 | hops += 1
|
|---|
| 693 | else:
|
|---|
| 694 | raise BadLabelType
|
|---|
| 695 | count = ord(message[current])
|
|---|
| 696 | current += 1
|
|---|
| 697 | if hops == 0:
|
|---|
| 698 | cused += 1
|
|---|
| 699 | labels.append('')
|
|---|
| 700 | return (Name(labels), cused)
|
|---|