+2005-11-12 Bob Halley <halley@dnspython.org>
+
+ * dns/name.py: Preliminary Unicode support has been added for
+ domain names. Running dns.name.from_text() on a Unicode string
+ will now encode each label using the IDN ACE encoding. The
+ to_unicode() method may be used to convert a dns.name.Name with
+ IDN ACE labels back into a Unicode string.
+
2005-10-31 Bob Halley <halley@dnspython.org>
* (Version 1.3.5 released)
if keyname is None:
self.keyname = self.keyring.keys()[0]
else:
- if isinstance(keyname, str):
+ if isinstance(keyname, (str, unicode)):
keyname = dns.name.from_text(keyname)
self.keyname = keyname
self.fudge = fudge
@type rdclass: int
@rtype: dns.message.Message object"""
- if isinstance(qname, str):
+ if isinstance(qname, (str, unicode)):
qname = dns.name.from_text(qname)
if isinstance(rdtype, str):
rdtype = dns.rdatatype.from_text(rdtype)
"""
import cStringIO
-import string
import struct
import sys
+if sys.hexversion >= 0x02030000:
+ import encodings.idna
+
import dns.exception
NAMERELN_NONE = 0
l = self.labels[:-1]
else:
l = self.labels
- s = string.join(map(_escapify, l), '.')
+ s = '.'.join(map(_escapify, l))
+ return s
+
+ def to_unicode(self, omit_final_dot = False):
+ """Convert name to Unicode text format.
+
+ IDN ACE lables are converted to Unicode.
+
+ @param omit_final_dot: If True, don't emit the final dot (denoting the
+ root label) for absolute names. The default is False.
+ @rtype: string
+ """
+
+ if len(self.labels) == 0:
+ return u'@'
+ if len(self.labels) == 1 and self.labels[0] == '':
+ return u'.'
+ if omit_final_dot and self.is_absolute():
+ l = self.labels[:-1]
+ else:
+ l = self.labels
+ s = u'.'.join([encodings.idna.ToUnicode(_escapify(x)) for x in l])
return s
def to_digestable(self, origin=None):
root = Name([''])
empty = Name([])
+def from_unicode(text, origin = root):
+ """Convert unicode text into a Name object.
+
+ Lables are encoded in IDN ACE form.
+
+ @rtype: dns.name.Name object
+ """
+
+ if not isinstance(text, unicode):
+ raise ValueError, "input to from_unicode() must be a unicode string"
+ if not (origin is None or isinstance(origin, Name)):
+ raise ValueError, "origin must be a Name or None"
+ labels = []
+ label = u''
+ escaping = False
+ edigits = 0
+ total = 0
+ if text == u'@':
+ text = u''
+ if text:
+ if text == u'.':
+ return Name(['']) # no Unicode "u" on this constant!
+ for c in text:
+ if escaping:
+ if edigits == 0:
+ if c.isdigit():
+ total = int(c)
+ edigits += 1
+ else:
+ label += c
+ escaping = False
+ else:
+ if not c.isdigit():
+ raise BadEscape
+ total *= 10
+ total += int(c)
+ edigits += 1
+ if edigits == 3:
+ escaping = False
+ label += chr(total)
+ elif c == u'.' or c == u'\u3002' or \
+ c == u'\uff0e' or c == u'\uff61':
+ if len(label) == 0:
+ raise EmptyLabel
+ labels.append(encodings.idna.ToASCII(label))
+ label = u''
+ elif c == u'\\':
+ escaping = True
+ edigits = 0
+ total = 0
+ else:
+ label += c
+ if escaping:
+ raise BadEscape
+ if len(label) > 0:
+ labels.append(encodings.idna.ToASCII(label))
+ else:
+ labels.append('')
+ if (len(labels) == 0 or labels[-1] != '') and not origin is None:
+ labels.extend(list(origin.labels))
+ return Name(labels)
+
def from_text(text, origin = root):
"""Convert text into a Name object.
@rtype: dns.name.Name object
"""
if not isinstance(text, str):
- raise ValueError, "input to from_text() must be a byte string"
+ if isinstance(text, unicode) and sys.hexversion >= 0x02030000:
+ return from_unicode(text, origin)
+ else:
+ raise ValueError, "input to from_text() must be a string"
if not (origin is None or isinstance(origin, Name)):
raise ValueError, "origin must be a Name or None"
labels = []
The default is 0.
@type source_port: int"""
- if isinstance(zone, str):
+ if isinstance(zone, (str, unicode)):
zone = dns.name.from_text(zone)
q = dns.message.make_query(zone, rdtype, rdclass)
if not keyring is None:
@raises NoNameservers: no non-broken nameservers are available to
answer the question."""
- if isinstance(qname, str):
+ if isinstance(qname, (str, unicode)):
qname = dns.name.from_text(qname, None)
if isinstance(rdtype, str):
rdtype = dns.rdatatype.from_text(rdtype)
@type resolver: dns.resolver.Resolver object or None
@rtype: dns.name.Name"""
- if isinstance(name, str):
+ if isinstance(name, (str, unicode)):
name = dns.name.from_text(name, dns.name.root)
if resolver is None:
resolver = get_default_resolver()
@rtype: dns.rrset.RRset object
"""
- if isinstance(name, str):
+ if isinstance(name, (str, unicode)):
name = dns.name.from_text(name, None)
if isinstance(rdclass, str):
rdclass = dns.rdataclass.from_text(rdclass)
@rtype: dns.rrset.RRset object
"""
- if isinstance(name, str):
+ if isinstance(name, (str, unicode)):
name = dns.name.from_text(name, None)
if len(rdatas) == 0:
"""
super(Update, self).__init__()
self.flags |= dns.opcode.to_flags(dns.opcode.UPDATE)
- if isinstance(zone, str):
+ if isinstance(zone, (str, unicode)):
zone = dns.name.from_text(zone)
else:
zone = zone.copy()
- ttl, rdtype, string..."""
- if isinstance(name, str):
+ if isinstance(name, (str, unicode)):
name = dns.name.from_text(name, None)
if isinstance(args[0], dns.rdataset.Rdataset):
for rds in args:
- rdtype, [string...]"""
- if isinstance(name, str):
+ if isinstance(name, (str, unicode)):
name = dns.name.from_text(name, None)
if len(args) == 0:
rrset = self.find_rrset(self.authority, name, dns.rdataclass.ANY,
- rdtype, string..."""
- if isinstance(name, str):
+ if isinstance(name, (str, unicode)):
name = dns.name.from_text(name, None)
if len(args) == 0:
rrset = self.find_rrset(self.answer, name,
"""Require that an owner name (and optionally an rdata type) does
not exist as a prerequisite to the execution of the update."""
- if isinstance(name, str):
+ if isinstance(name, (str, unicode)):
name = dns.name.from_text(name, None)
if rdtype is None:
rrset = self.find_rrset(self.answer, name,
return not self.__eq__(other)
def _validate_name(self, name):
- if isinstance(name, str):
+ if isinstance(name, (str, unicode)):
name = dns.name.from_text(name, None)
elif not isinstance(name, dns.name.Name):
raise KeyError, \
def __init__(self, tok, origin, rdclass, relativize, zone_factory=Zone,
allow_include=False):
- if isinstance(origin, str):
+ if isinstance(origin, (str, unicode)):
origin = dns.name.from_text(origin)
self.tok = tok
self.current_origin = origin
n.parent()
self.failUnlessRaises(dns.name.NoParent, bad)
+ def testFromUnicode1(self):
+ n = dns.name.from_text(u'foo.bar')
+ self.failUnless(n.labels == ('foo', 'bar', ''))
+
+ def testFromUnicode2(self):
+ n = dns.name.from_text(u'foo\u1234bar.bar')
+ self.failUnless(n.labels == ('xn--foobar-r5z', 'bar', ''))
+
+ def testFromUnicodeAlternateDot1(self):
+ n = dns.name.from_text(u'foo\u3002bar')
+ self.failUnless(n.labels == ('foo', 'bar', ''))
+
+ def testFromUnicodeAlternateDot2(self):
+ n = dns.name.from_text(u'foo\uff0ebar')
+ self.failUnless(n.labels == ('foo', 'bar', ''))
+
+ def testFromUnicodeAlternateDot3(self):
+ n = dns.name.from_text(u'foo\uff61bar')
+ self.failUnless(n.labels == ('foo', 'bar', ''))
+
+ def testToUnicode1(self):
+ n = dns.name.from_text(u'foo.bar')
+ s = n.to_unicode()
+ self.failUnless(s == u'foo.bar.')
+
+ def testToUnicode2(self):
+ n = dns.name.from_text(u'foo\u1234bar.bar')
+ s = n.to_unicode()
+ self.failUnless(s == u'foo\u1234bar.bar.')
+
+ def testToUnicode3(self):
+ n = dns.name.from_text('foo.bar')
+ s = n.to_unicode()
+ self.failUnless(s == u'foo.bar.')
+
if __name__ == '__main__':
unittest.main()