From: Bob Halley Date: Wed, 21 Sep 2016 12:44:37 +0000 (-0700) Subject: Only use IDNA 2008 if requested. X-Git-Tag: v1.15.0~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=46b8804e1e96153d74282c656d030be54c8569a2;p=thirdparty%2Fdnspython.git Only use IDNA 2008 if requested. --- diff --git a/ChangeLog b/ChangeLog index 78a2961a..1b699330 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,11 +1,7 @@ 2016-09-20 Bob Halley - * 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. diff --git a/dns/name.py b/dns/name.py index 41ae4ca9..2d27ce10 100644 --- a/dns/name.py +++ b/dns/name.py @@ -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)): diff --git a/tests/test_name.py b/tests/test_name.py index 03136cfd..7a2ecd0b 100644 --- a/tests/test_name.py +++ b/tests/test_name.py @@ -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'ドメイン.テスト.')