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)
|
---|