]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
Make IDNA default decode just decode the punycode.
authorBob Halley <halley@dnspython.org>
Thu, 29 Sep 2016 15:25:50 +0000 (08:25 -0700)
committerBob Halley <halley@dnspython.org>
Thu, 29 Sep 2016 15:25:50 +0000 (08:25 -0700)
dns/name.py
tests/test_name.py

index af99e443f8750a2f0891a31f85b6e496a6f7b93e..fabdea6e1646460356f7eeb6d137e16c2c712f69 100644 (file)
@@ -104,7 +104,7 @@ class NoIDNA2008(dns.exception.DNSException):
 
 class IDNAException(dns.exception.DNSException):
 
-    """IDNA 2008 processing raised an exception."""
+    """IDNA processing raised an exception."""
 
     supp_kwargs = set(['idna_exception'])
     fmt = "IDNA processing exception: {idna_exception}"
@@ -113,16 +113,38 @@ class IDNACodec(object):
 
     """Abstract base class for IDNA encoder/decoders."""
 
+    def __init__(self):
+        pass
+
     def encode(self, label):
         raise NotImplementedError
 
     def decode(self, label):
-        raise NotImplementedError
+        # We do not apply any IDNA policy on decode; we just
+        downcased = label.lower()
+        if downcased.startswith(b'xn--'):
+            try:
+                label = downcased[4:].decode('punycode')
+            except Exception as e:
+                raise IDNAException(idna_exception=e)
+        else:
+            label = maybe_decode(label)
+        return _escapify(label, True)
 
 class IDNA2003Codec(IDNACodec):
 
     """IDNA 2003 encoder/decoder."""
 
+    def __init__(self, strict_decode=False):
+        """Initialize the IDNA 2003 encoder/decoder.
+        @param strict_decode: If True, then IDNA2003 checking is done when
+        decoding.  This can cause failures if the name was encoded with
+        IDNA2008.  The default is False.
+        @type strict_decode: bool
+        """
+        super(IDNA2003Codec, self).__init__()
+        self.strict_decode = strict_decode
+
     def encode(self, label):
         if label == '':
             return b''
@@ -132,16 +154,21 @@ class IDNA2003Codec(IDNACodec):
             raise LabelTooLong
 
     def decode(self, label):
+        if not self.strict_decode:
+            return super(IDNA2003Codec, self).decode(label)
         if label == b'':
             return u''
-        return _escapify(encodings.idna.ToUnicode(label), True)
+        try:
+            return _escapify(encodings.idna.ToUnicode(label), True)
+        except Exception as e:
+            raise IDNAException(idna_exception=e)
 
 class IDNA2008Codec(IDNACodec):
 
     """IDNA 2008 encoder/decoder."""
 
     def __init__(self, uts_46=False, transitional=False,
-                 allow_pure_ascii=False):
+                 allow_pure_ascii=False, strict_decode=False):
         """Initialize the IDNA 2008 encoder/decoder.
         @param uts_46: If True, apply Unicode IDNA compatibility processing
         as described in Unicode Technical Standard #46
@@ -159,10 +186,16 @@ class IDNA2008Codec(IDNACodec):
         e.g. a name with starting with "_sip._tcp." and ending in an IDN
         suffixm which would otherwise be disallowed.  The default is False
         @type allow_pure_ascii: bool
+        @param strict_decode: If True, then IDNA2008 checking is done when
+        decoding.  This can cause failures if the name was encoded with
+        IDNA2003.  The default is False.
+        @type strict_decode: bool
         """
+        super(IDNA2008Codec, self).__init__()
         self.uts_46 = uts_46
         self.transitional = transitional
         self.allow_pure_ascii = allow_pure_ascii
+        self.strict_decode = strict_decode
 
     def is_all_ascii(self, label):
         for c in label:
@@ -185,6 +218,8 @@ class IDNA2008Codec(IDNACodec):
             raise IDNAException(idna_exception=e)
 
     def decode(self, label):
+        if not self.strict_decode:
+            return super(IDNA2008Codec, self).decode(label)
         if label == b'':
             return u''
         if not have_idna_2008:
@@ -196,14 +231,15 @@ class IDNA2008Codec(IDNACodec):
         except idna.IDNAError as e:
             raise IDNAException(idna_exception=e)
 
-
 _escaped = bytearray(b'"().;\\@$')
 
-IDNA_2003 = IDNA2003Codec()
-IDNA_2008_Practical = IDNA2008Codec(True, False, True)
-IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False)
-IDNA_2008_Strict = IDNA2008Codec(False, False, False)
-IDNA_2008_Transitional = IDNA2008Codec(True, True, False)
+IDNA_2003_Practical = IDNA2003Codec(False)
+IDNA_2003_Strict = IDNA2003Codec(True)
+IDNA_2003 = IDNA_2003_Practical
+IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)
+IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)
+IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)
+IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)
 IDNA_2008 = IDNA_2008_Practical
 
 def _escapify(label, unicode_mode=False):
@@ -494,9 +530,11 @@ 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: idna_codec: IDNA encoder/decoder.  If None, the default IDNA
-        2003
-        encoder/decoder is used.
+        @param: idna_codec: IDNA encoder/decoder.  If None, the
+        IDNA_2003_Practical encoder/decoder is used.  The IDNA_2003_Practical
+        decoder does not impose any policy, it just decodes punycode, so if
+        you don't want checking for compliance, you can use this decoder for
+        IDNA2008 as well.
         @type idna_codec: dns.name.IDNA
         @rtype: string
         """
@@ -510,7 +548,7 @@ class Name(object):
         else:
             l = self.labels
         if idna_codec is None:
-            idna_codec = IDNA_2003
+            idna_codec = IDNA_2003_Practical
         return u'.'.join([idna_codec.decode(x) for x in l])
 
     def to_digestable(self, origin=None):
index 999e5e471811325565afcc68d84d192c78de339d..f2a87736ffcf9b90d2be2df8ab462cc5ca52c5b4 100644 (file)
@@ -722,6 +722,21 @@ class NameTestCase(unittest.TestCase):
             self.assertEqual(str(n), 'xn--eckwd4c7c.xn--zckzah.')
             self.assertEqual(s, u'ドメイン.テスト.')
 
+    def testDefaultDecodeIsJustPunycode(self):
+        # groß.com. in IDNA2008 form, pre-encoded.
+        n = dns.name.from_text('xn--gro-7ka.com')
+        # output using default codec which just decodes the punycode and
+        # doesn't test for IDNA2003 or IDNA2008.
+        self.assertEqual(n.to_unicode(), u'groß.com.')
+
+    def testStrictINDA2003Decode(self):
+        # groß.com. in IDNA2008 form, pre-encoded.
+        n = dns.name.from_text('xn--gro-7ka.com')
+        def bad():
+            # This throws in IDNA2003 because it doesn't "round trip".
+            n.to_unicode(idna_codec=dns.name.IDNA_2003_Strict)
+        self.failUnlessRaises(dns.name.IDNAException, bad)
+
     def testReverseIPv4(self):
         e = dns.name.from_text('1.0.0.127.in-addr.arpa.')
         n = dns.reversename.from_address('127.0.0.1')