]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
Only use IDNA 2008 if requested.
authorBob Halley <halley@dnspython.org>
Wed, 21 Sep 2016 12:44:37 +0000 (05:44 -0700)
committerBob Halley <halley@dnspython.org>
Wed, 21 Sep 2016 12:44:37 +0000 (05:44 -0700)
ChangeLog
dns/name.py
tests/test_name.py

index 78a2961a43be8e402a2d224fadf7045a2e40154e..1b6993303a9e083efc64b30467c7b20452f7cc45 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,11 +1,7 @@
 2016-09-20  Bob Halley  <halley@dnspython.org>
 
-       * If the "idna" module is installed ("pip install idna"), then
-         dnspython will use IDNA 2008 instead of IDNA 2003.  Options have
-         been added to unicode name routines for UTS46 support
-         including "STD3 rules" and "transitional".  See
-         http://unicode.org/reports/tr46/ for more info on what this
-         means.
+       * IDNA 2008 support is now available on request if the "idna"
+         module has been installed.
 
        * Add AVC RR support.
 
index 41ae4ca9fc5c07a5bcc0fd6b3ef3b49ce1b25662..2d27ce103e26da52b8fbd2368d86beb6a3b06c86 100644 (file)
@@ -96,6 +96,11 @@ class NoParent(dns.exception.DNSException):
     """An attempt was made to get the parent of the root name
     or the empty name."""
 
+class NoIDNA2008(dns.exception.DNSException):
+
+    """IDNA 2008 processing was requested but the idna module is not
+    available."""
+
 _escaped = bytearray(b'"().;\\@$')
 
 
@@ -131,11 +136,13 @@ def _escapify(label, unicode_mode=False):
                 text += u'\\%03d' % ord(c)
     return text
 
-def _idna_encode(label, uts46, std3_rules, transitional):
+def _idna_encode(label, idna_2008, uts_46, std3_rules, transitional):
     if label == '':
         return b''
-    if have_idna_2008:
-        if uts46:
+    if idna_2008:
+        if not have_idna_2008:
+            raise NoIDNA2008
+        if uts_46:
             label = idna.uts46_remap(label, std3_rules, transitional)
         label = idna.alabel(label)
     else:
@@ -145,11 +152,13 @@ def _idna_encode(label, uts46, std3_rules, transitional):
             raise LabelTooLong
     return label
 
-def _idna_decode(label, uts46, std3_rules):
+def _idna_decode(label, idna_2008, uts_46, std3_rules):
     if label == b'':
         return u''
-    if have_idna_2008:
-        if uts46:
+    if idna_2008:
+        if not have_idna_2008:
+            raise NoIDNA2008
+        if uts_46:
             label = idna.uts46_remap(label, std3_rules, False)
         label = idna.ulabel(label)
     else:
@@ -404,7 +413,7 @@ class Name(object):
         s = b'.'.join(map(_escapify, l))
         return s
 
-    def to_unicode(self, omit_final_dot=False, uts46=False,
+    def to_unicode(self, omit_final_dot=False, idna_2008=False, uts_46=False,
                    std3_rules=False):
         """Convert name to Unicode text format.
 
@@ -413,10 +422,14 @@ class Name(object):
         @param omit_final_dot: If True, don't emit the final dot (denoting the
         root label) for absolute names.  The default is False.
         @type omit_final_dot: bool
-        @param uts46: If True, apply Unicode IDNA compatibility processing
+        @param: idna_2008: If True, IDNA 2008 will be used instead of IDNA 2003.
+        If the "idna" module is not available, a NoIDNA2008 exception will be
+        raised.
+        @type: idna_2008: bool
+        @param uts_46: If True, apply Unicode IDNA compatibility processing
         as described in Unicode Technical Standard #46
         (U{http://unicode.org/reports/tr46/})
-        @type uts46: bool
+        @type uts_46: bool
         @param std3_rules: If True, apply STD3 rules for hostnames.
         (You should only set this to True if you want to be very strict
         about hostnames, and it's not appropropriate for domain names in
@@ -433,7 +446,8 @@ class Name(object):
             l = self.labels[:-1]
         else:
             l = self.labels
-        return u'.'.join([_idna_decode(x, uts46, std3_rules) for x in l])
+        return u'.'.join([_idna_decode(x, idna_2008, uts_46, std3_rules)
+                          for x in l])
 
     def to_digestable(self, origin=None):
         """Convert name to a format suitable for digesting in hashes.
@@ -618,8 +632,8 @@ root = Name([b''])
 empty = Name([])
 
 
-def from_unicode(text, origin=root, uts46=False, std3_rules=False,
-                 transitional=False):
+def from_unicode(text, origin=root, idna_2008=False, uts_46=False,
+                 std3_rules=False, transitional=False):
     """Convert unicode text into a Name object.
 
     Labels are encoded in IDN ACE form.
@@ -628,17 +642,23 @@ def from_unicode(text, origin=root, uts46=False, std3_rules=False,
     @type text: Unicode string
     @param origin: The origin to append to non-absolute names.
     @type origin: dns.name.Name
-    @param uts46: If True, apply Unicode IDNA compatibility processing
+    @param: idna_2008: If True, IDNA 2008 will be used instead of IDNA 2003.
+    If the "idna" module is not available, a NoIDNA2008 exception will be
+    raised.
+    @type: idna_2008: bool
+    @param uts_46: If True, apply Unicode IDNA compatibility processing
     as described in Unicode Technical Standard #46
-    (U{http://unicode.org/reports/tr46/})
-    @type uts46: bool
+    (U{http://unicode.org/reports/tr46/}).  This parameter is only
+    meaningful if IDNA 2008 is in use.
+    @type uts_46: bool
     @param std3_rules: If True, apply STD3 rules for hostnames.
     (You should only set this to True if you want to be very strict
     about hostnames, and it's not appropropriate for domain names in
-    general.
+    general.  This parameter is only meaningful if IDNA 2008 is in use.
     @type std3_rules: bool
     @param transitional: If True, use the "transitional" mode described
-    in Unicode Technical Standard #46.
+    in Unicode Technical Standard #46.  This parameter is only
+    meaningful if IDNA 2008 is in use.
     @type transitional: bool
     @rtype: dns.name.Name object
     """
@@ -678,8 +698,8 @@ def from_unicode(text, origin=root, uts46=False, std3_rules=False,
             elif c in [u'.', u'\u3002', u'\uff0e', u'\uff61']:
                 if len(label) == 0:
                     raise EmptyLabel
-                labels.append(_idna_encode(label, uts46, std3_rules,
-                                           transitional))
+                labels.append(_idna_encode(label, idna_2008, uts_46,
+                                           std3_rules, transitional))
                 label = u''
             elif c == u'\\':
                 escaping = True
@@ -690,8 +710,8 @@ def from_unicode(text, origin=root, uts46=False, std3_rules=False,
         if escaping:
             raise BadEscape
         if len(label) > 0:
-            labels.append(_idna_encode(label, uts46, std3_rules,
-                                       transitional))
+            labels.append(_idna_encode(label, idna_2008, uts_46,
+                                       std3_rules, transitional))
         else:
             labels.append(b'')
 
@@ -700,14 +720,38 @@ def from_unicode(text, origin=root, uts46=False, std3_rules=False,
     return Name(labels)
 
 
-def from_text(text, origin=root, uts46=False, std3_rules=False,
-              transitional=False):
+def from_text(text, origin=root, idna_2008=False, uts_46=False,
+              std3_rules=False, transitional=False):
     """Convert text into a Name object.
+
+    @param text: The text to convert into a name.
+    @type text: string
+    @param origin: The origin to append to non-absolute names.
+    @type origin: dns.name.Name
+    @param: idna_2008: If True, IDNA 2008 will be used instead of IDNA 2003.
+    If the "idna" module is not available, a NoIDNA2008 exception will be
+    raised.
+    @type: idna_2008: bool
+    @param uts_46: If True, apply Unicode IDNA compatibility processing
+    as described in Unicode Technical Standard #46
+    (U{http://unicode.org/reports/tr46/}).  This parameter is only
+    meaningful if IDNA 2008 is in use.
+    @type uts_46: bool
+    @param std3_rules: If True, apply STD3 rules for hostnames.
+    (You should only set this to True if you want to be very strict
+    about hostnames, and it's not appropropriate for domain names in
+    general.  This parameter is only meaningful if IDNA 2008 is in use.
+    @type std3_rules: bool
+    @param transitional: If True, use the "transitional" mode described
+    in Unicode Technical Standard #46.  This parameter is only
+    meaningful if IDNA 2008 is in use.
+    @type transitional: bool
     @rtype: dns.name.Name object
     """
 
     if isinstance(text, text_type):
-        return from_unicode(text, origin, uts46, std3_rules, transitional)
+        return from_unicode(text, origin, idna_2008, uts_46, 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 03136cfd8f15c540e2a6cb15a2387ff251d13f05..7a2ecd0b89ed9e7ddf019e8121f9c298c3a591bf 100644 (file)
@@ -29,7 +29,6 @@ import dns.e164
 
 if dns.name.have_idna_2008:
     import idna
-dns.name.have_idna_2008 = False # XXXRTH
 
 # pylint: disable=line-too-long
 
@@ -248,13 +247,8 @@ class NameTestCase(unittest.TestCase):
 
     def testToText9(self):
         n = dns.name.from_text('FOO bar', origin=None)
-        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')
+        t = n.to_unicode()
+        self.assertEqual(t, r'FOO\032bar')
 
     def testToText10(self):
         t = dns.name.empty.to_unicode()
@@ -665,11 +659,12 @@ class NameTestCase(unittest.TestCase):
         if dns.name.have_idna_2008:
             t = u'Königsgäßchen'
             def bad():
-                return dns.name.from_unicode(t)
+                return dns.name.from_unicode(t, idna_2008=True)
             self.failUnlessRaises(idna.InvalidCodepoint, bad)
-            e1 = dns.name.from_unicode(t, uts46=True)
+            e1 = dns.name.from_unicode(t, idna_2008=True, uts_46=True)
             self.assertEqual(str(e1), b'xn--knigsgchen-b4a3dun.')
-            e2 = dns.name.from_unicode(t, uts46=True, transitional=True)
+            e2 = dns.name.from_unicode(t, idna_2008=True, uts_46=True,
+                                       transitional=True)
             self.assertEqual(str(e2), b'xn--knigsgsschen-lcb0w.')
 
     def testToUnicode1(self):
@@ -689,7 +684,7 @@ class NameTestCase(unittest.TestCase):
 
     def testToUnicode4(self):
         if dns.name.have_idna_2008:
-            n = dns.name.from_text(u'ドメイン.テスト')
+            n = dns.name.from_text(u'ドメイン.テスト', idna_2008=True)
             s = n.to_unicode()
             self.assertEqual(str(n), b'xn--eckwd4c7c.xn--zckzah.')
             self.assertEqual(s, u'ドメイン.テスト.')