]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
If the IDNA2008 module "idna" is available, use it and do IDNA 2008 encoding.
authorBob Halley <halley@dnspython.org>
Mon, 19 Sep 2016 17:12:08 +0000 (10:12 -0700)
committerBob Halley <halley@dnspython.org>
Mon, 19 Sep 2016 17:12:13 +0000 (10:12 -0700)
.travis.yml
dns/name.py
tests/test_name.py

index a88237d4f724237bd00a0dbe2e54cbf6e2d72fd6..a7343ac977d0b0444bb852de64146c72de175f4a 100644 (file)
@@ -15,7 +15,7 @@ matrix:
   allow_failures:
     - python: nightly
 install:
- - pip install unittest2 pylint
+ - pip install unittest2 pylint pycrypto ecdsa idna
 script:
  - if [[ $TRAVIS_PYTHON_VERSION != '2.6' ]]; then make lint; fi
  - make test
index 98ad321da5590488f7d8205bc5d0c09ef0545ce5..8db89fa756f7f1a9ebfded623eeddfda9472b364 100644 (file)
@@ -26,6 +26,11 @@ import struct
 import sys
 import copy
 import encodings.idna
+try:
+    import idna
+    have_idna_2008 = True
+except ImportError:
+    have_idna_2008 = False
 
 import dns.exception
 import dns.wiredata
@@ -126,6 +131,30 @@ def _escapify(label, unicode_mode=False):
                 text += u'\\%03d' % ord(c)
     return text
 
+def _idna_encode(label, uts46, std3_rules, transitional):
+    if label == '':
+        return b''
+    if have_idna_2008:
+        if uts46:
+            label = idna.uts46_remap(label, std3_rules, transitional)
+        label = idna.alabel(label)
+    else:
+        try:
+            label = encodings.idna.ToASCII(label)
+        except UnicodeError:
+            raise LabelTooLong
+    return label
+
+def _idna_decode(label, uts46, std3_rules):
+    if label == b'':
+        return u''
+    if have_idna_2008:
+        if uts46:
+            label = idna.uts46_remap(label, std3_rules, False)
+        label = idna.ulabel(label)
+    else:
+        label = encodings.idna.ToUnicode(label)
+    return _escapify(label, True)
 
 def _validate_labels(labels):
     """Check for empty labels in the middle of a label sequence,
@@ -375,7 +404,8 @@ class Name(object):
         s = b'.'.join(map(_escapify, l))
         return s
 
-    def to_unicode(self, omit_final_dot=False):
+    def to_unicode(self, omit_final_dot=False, uts46=False,
+                   std3_rules=False):
         """Convert name to Unicode text format.
 
         IDN ACE labels are converted to Unicode.
@@ -393,9 +423,7 @@ class Name(object):
             l = self.labels[:-1]
         else:
             l = self.labels
-        s = u'.'.join([_escapify(encodings.idna.ToUnicode(x), True)
-                      for x in l])
-        return s
+        return u'.'.join([_idna_decode(x, uts46, std3_rules) for x in l])
 
     def to_digestable(self, origin=None):
         """Convert name to a format suitable for digesting in hashes.
@@ -580,7 +608,8 @@ root = Name([b''])
 empty = Name([])
 
 
-def from_unicode(text, origin=root):
+def from_unicode(text, origin=root, uts46=False, std3_rules=False,
+                 transitional=False):
     """Convert unicode text into a Name object.
 
     Labels are encoded in IDN ACE form.
@@ -623,10 +652,8 @@ def from_unicode(text, origin=root):
             elif c in [u'.', u'\u3002', u'\uff0e', u'\uff61']:
                 if len(label) == 0:
                     raise EmptyLabel
-                try:
-                    labels.append(encodings.idna.ToASCII(label))
-                except UnicodeError:
-                    raise LabelTooLong
+                labels.append(_idna_encode(label, uts46, std3_rules,
+                                           transitional))
                 label = u''
             elif c == u'\\':
                 escaping = True
@@ -637,10 +664,8 @@ def from_unicode(text, origin=root):
         if escaping:
             raise BadEscape
         if len(label) > 0:
-            try:
-                labels.append(encodings.idna.ToASCII(label))
-            except UnicodeError:
-                raise LabelTooLong
+            labels.append(_idna_encode(label, uts46, std3_rules,
+                                       transitional))
         else:
             labels.append(b'')
 
@@ -649,13 +674,14 @@ def from_unicode(text, origin=root):
     return Name(labels)
 
 
-def from_text(text, origin=root):
+def from_text(text, origin=root, uts46=False, std3_rules=False,
+              transitional=False):
     """Convert text into a Name object.
     @rtype: dns.name.Name object
     """
 
     if isinstance(text, text_type):
-        return from_unicode(text, origin)
+        return from_unicode(text, origin, uts46, std3_rules, transitional)
     if not isinstance(text, binary_type):
         raise ValueError("input to from_text() must be a string")
     if not (origin is None or isinstance(origin, Name)):
index 70ec1228c75de719163c1b1ffc7b04298bc1e9e5..03136cfd8f15c540e2a6cb15a2387ff251d13f05 100644 (file)
@@ -1,3 +1,4 @@
+# -*- coding: utf-8
 # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
 #
 # Permission to use, copy, modify, and distribute this software and its
@@ -26,6 +27,10 @@ import dns.name
 import dns.reversename
 import dns.e164
 
+if dns.name.have_idna_2008:
+    import idna
+dns.name.have_idna_2008 = False # XXXRTH
+
 # pylint: disable=line-too-long
 
 
@@ -243,8 +248,13 @@ class NameTestCase(unittest.TestCase):
 
     def testToText9(self):
         n = dns.name.from_text('FOO bar', origin=None)
-        t = n.to_unicode()
-        self.assertEqual(t, r'FOO\032bar')
+        if dns.name.have_idna_2008:
+            def bad():
+                return n.to_unicode()
+            self.failUnlessRaises(idna.InvalidCodepoint, bad)
+        else:
+            t = n.to_unicode()
+            self.assertEqual(t, r'FOO\032bar')
 
     def testToText10(self):
         t = dns.name.empty.to_unicode()
@@ -651,6 +661,17 @@ class NameTestCase(unittest.TestCase):
         n = dns.name.from_text(u'foo\uff61bar')
         self.assertEqual(n.labels, (b'foo', b'bar', b''))
 
+    def testFromUnicodeIDNA2008(self):
+        if dns.name.have_idna_2008:
+            t = u'Königsgäßchen'
+            def bad():
+                return dns.name.from_unicode(t)
+            self.failUnlessRaises(idna.InvalidCodepoint, bad)
+            e1 = dns.name.from_unicode(t, uts46=True)
+            self.assertEqual(str(e1), b'xn--knigsgchen-b4a3dun.')
+            e2 = dns.name.from_unicode(t, uts46=True, transitional=True)
+            self.assertEqual(str(e2), b'xn--knigsgsschen-lcb0w.')
+
     def testToUnicode1(self):
         n = dns.name.from_text(u'foo.bar')
         s = n.to_unicode()
@@ -666,6 +687,13 @@ class NameTestCase(unittest.TestCase):
         s = n.to_unicode()
         self.assertEqual(s, u'foo.bar.')
 
+    def testToUnicode4(self):
+        if dns.name.have_idna_2008:
+            n = dns.name.from_text(u'ドメイン.テスト')
+            s = n.to_unicode()
+            self.assertEqual(str(n), b'xn--eckwd4c7c.xn--zckzah.')
+            self.assertEqual(s, u'ドメイン.テスト.')
+
     def testReverseIPv4(self):
         e = dns.name.from_text('1.0.0.127.in-addr.arpa.')
         n = dns.reversename.from_address('127.0.0.1')