From: Bob Halley Date: Mon, 19 Sep 2016 17:12:08 +0000 (-0700) Subject: If the IDNA2008 module "idna" is available, use it and do IDNA 2008 encoding. X-Git-Tag: v1.15.0~22 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=58e84f91047966ad36562cd19bd89c38680f3d8c;p=thirdparty%2Fdnspython.git If the IDNA2008 module "idna" is available, use it and do IDNA 2008 encoding. --- diff --git a/.travis.yml b/.travis.yml index a88237d4..a7343ac9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/dns/name.py b/dns/name.py index 98ad321d..8db89fa7 100644 --- a/dns/name.py +++ b/dns/name.py @@ -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)): diff --git a/tests/test_name.py b/tests/test_name.py index 70ec1228..03136cfd 100644 --- a/tests/test_name.py +++ b/tests/test_name.py @@ -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')