]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
Preliminary Unicode support
authorBob Halley <halley@dnspython.org>
Sun, 13 Nov 2005 01:54:29 +0000 (01:54 +0000)
committerBob Halley <halley@dnspython.org>
Sun, 13 Nov 2005 01:54:29 +0000 (01:54 +0000)
ChangeLog
dns/message.py
dns/name.py
dns/query.py
dns/resolver.py
dns/rrset.py
dns/update.py
dns/zone.py
tests/name.py

index b4cef236e23776a613369f1193badd9aad633c3e..29f8964646e65331855b7b7b40de9d2d15273762 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+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)
index 335d4fe155f15371eba06bde0f6791f5e3ba2c53..0995299ccbe02a8654bf90fe0f2c77528d74a534 100644 (file)
@@ -438,7 +438,7 @@ class Message(object):
         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
@@ -954,7 +954,7 @@ def make_query(qname, rdtype, rdclass = dns.rdataclass.IN):
     @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)
index 09e2c6e64d0bd6233be30de5881cd790437f57cc..9f7b6d1bd6a28c4b673af9705d21dac132cab353 100644 (file)
 """
 
 import cStringIO
-import string
 import struct
 import sys
 
+if sys.hexversion >= 0x02030000:
+    import encodings.idna
+
 import dns.exception
 
 NAMERELN_NONE = 0
@@ -321,7 +323,28 @@ class Name(object):
             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):
@@ -508,13 +531,78 @@ class Name(object):
 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 = []
index 943d14e962d703f3e7e8c435c910595dc90bee58..38bbcafd2d7990ca6e7e498d7e2cad4b9e2bcc95 100644 (file)
@@ -263,7 +263,7 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
     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:
index 440323866f31e36068cb961a94414920f33b09be..4b58e79e586ac08f3b86beaea8b10125a265d3a4 100644 (file)
@@ -474,7 +474,7 @@ class Resolver(object):
         @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)
@@ -642,7 +642,7 @@ def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None):
     @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()
index 82638e26ed81b63b3d70c4b05951a9d22c603105..f9b873675899b16bc3943f396f84e534fb282e6e 100644 (file)
@@ -115,7 +115,7 @@ def from_text_list(name, ttl, rdclass, rdtype, text_rdatas):
     @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)
@@ -144,7 +144,7 @@ def from_rdata_list(name, ttl, rdatas):
     @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:
index 4595c96894f6c06eab76c352ad8acf21c76d7c2b..c513647a9247309e168a907cc2dcca705ca09bd7 100644 (file)
@@ -44,7 +44,7 @@ class Update(dns.message.Message):
         """
         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()
@@ -80,7 +80,7 @@ class Update(dns.message.Message):
 
                 - 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:
@@ -130,7 +130,7 @@ class Update(dns.message.Message):
 
                 - 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,
@@ -188,7 +188,7 @@ class Update(dns.message.Message):
 
                 - 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,
@@ -216,7 +216,7 @@ class Update(dns.message.Message):
         """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,
index c875a31c98d0e3c5fd184a66af888fc10d075ead..085a6a12ef61e2a7fd8d76b56a9a74468ccf1381 100644 (file)
@@ -102,7 +102,7 @@ class Zone(object):
         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, \
@@ -543,7 +543,7 @@ class _MasterReader(object):
 
     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
index cc7ced5f000eb903bd758a2d03e87b034c72089e..07e2c66f42c299755a2400ee1b7eca36cb6b4af7 100644 (file)
@@ -611,5 +611,40 @@ class NameTestCase(unittest.TestCase):
             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()