diff --git a/formencode/validators.py b/formencode/validators.py index 75573a6c..d3a2b76e 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -16,10 +16,9 @@ from sets import Set as set try: - import DNS - DNS.DiscoverNameServers() - # DNS.DiscoverNameServers() raises IOError when network isn't available - # on BSD/Mac OS X + import dns.resolver + import dns.exception + from encodings import idna have_dns = True except (IOError, ImportError): have_dns = False @@ -1227,7 +1226,7 @@ class Email(FancyValidator): If you pass ``resolve_domain=True``, then it will try to resolve the domain name to make sure it's valid. This takes longer, of - course. You must have the `pyDNS `__ modules + course. You must have the `dnspython `__ modules installed to look up DNS (MX and A) records. :: @@ -1266,7 +1265,7 @@ class Email(FancyValidator): 'doesnotexist@colorstudy.com' >>> e.to_python('test@nyu.edu') 'test@nyu.edu' - >>> # NOTE: If you do not have PyDNS installed this example won't work: + >>> # NOTE: If you do not have dnspython installed this example won't work: >>> e.to_python('test@thisdomaindoesnotexistithinkforsure.com') Traceback (most recent call last): ... @@ -1304,10 +1303,10 @@ def __init__(self, *args, **kw): if self.resolve_domain: if not have_dns: warnings.warn( - "pyDNS is not installed on" - " your system (or the DNS package cannot be found)." - " I cannot resolve domain names in addresses") - raise ImportError("no module named DNS") + "dnspython is not installed on" + " your system (or the dns.resolver package cannot be found)." + " I cannot resolve domain names in addresses") + raise ImportError("no module named dns.resolver") def validate_python(self, value, state): if not value: @@ -1322,29 +1321,40 @@ def validate_python(self, value, state): raise Invalid( self.message('badUsername', state, username=username), value, state) - if not self.domainRE.search(domain): + try: + idna_domain = '.'.join([idna.ToASCII(l) for l in domain.split('.')]) + except UnicodeError: + # UnicodeError: label empty or too long + # This exception might happen if we have an invalid domain name part + # (for example test@.foo.bar.com) + raise Invalid( + self.message('badDomain', state, domain=domain), + value, state) + if not self.domainRE.search(idna_domain): raise Invalid( self.message('badDomain', state, domain=domain), value, state) if self.resolve_domain: - assert have_dns, "pyDNS should be available" + assert have_dns, "dnspython should be available" global socket if socket is None: import socket try: - answers = DNS.DnsRequest(domain, qtype='a', - timeout=self.resolve_timeout).req().answers - if answers: - answers = DNS.DnsRequest(domain, qtype='mx', - timeout=self.resolve_timeout).req().answers - except (socket.error, DNS.DNSError), e: + try: + a = dns.resolver.query(domain, 'MX') + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer), e: + try: + a = dns.resolver.query(domain, 'A') + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer), e: + raise Invalid( + self.message('domainDoesNotExist', state, domain=domain), + value, state + ) + except (socket.error, dns.exception.DNSException), e: raise Invalid( self.message('socketError', state, error=e), - value, state) - if not answers: - raise Invalid( - self.message('domainDoesNotExist', state, domain=domain), - value, state) + value, state + ) def _to_python(self, value, state): return value.strip() diff --git a/setup.py b/setup.py index c3095554..0344165a 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ if not '2.3' <= sys.version < '3.0': raise ImportError('Python version not supported') -tests_require = ['nose', 'pycountry', 'pyDNS'] +tests_require = ['nose', 'pycountry', 'dnspython'] if sys.version < '2.5': tests_require.append('elementtree') diff --git a/tests/test_email.py b/tests/test_email.py index e8e1c0e2..84991142 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from unittest import TestCase from nose.tools import assert_equal from formencode.validators import Email, Invalid @@ -69,3 +70,15 @@ def expected_message(validator, message_name, username, domain): for email, expected in valid_email_addresses: yield assert_equal, _validate(validator, email), expected + + +class TestUnicodeEmailWithResolveDomain(TestCase): + + def setUp(self): + self.validator = Email(resolve_domain=True) + + def test_unicode_ascii_subgroup(self): + self.assertEqual(self.validator.to_python(u'foo@yandex.com'), 'foo@yandex.com') + + def test_cyrillic_email(self): + self.assertEqual(self.validator.to_python(u'me@письмо.рф'), u'me@письмо.рф') diff --git a/tox.ini b/tox.ini index 9e54e0f0..89ba0f5d 100644 --- a/tox.ini +++ b/tox.ini @@ -3,12 +3,12 @@ envlist=py24,py25,py26,py27 [testenv] deps=nose - pyDNS + dnspython pycountry commands=nosetests [testenv:py24] deps=ElementTree nose - pyDNS + dnspython pycountry