Fixed #9764 - Updated EmailField and URLField to support IDN (Internationalized Domain Names). Thanks, UloPe.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12474 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2010-02-21 23:44:35 +00:00
parent 0d4726f518
commit 7f5d9ad661
3 changed files with 56 additions and 2 deletions

View File

@ -1,4 +1,5 @@
import re import re
import urlparse
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -52,7 +53,29 @@ class URLValidator(RegexValidator):
self.user_agent = validator_user_agent self.user_agent = validator_user_agent
def __call__(self, value): def __call__(self, value):
try:
super(URLValidator, self).__call__(value) super(URLValidator, self).__call__(value)
except ValidationError, e:
# Trivial case failed. Try for possible IDN domain
if value:
original = value
value = smart_unicode(value)
splitted = urlparse.urlsplit(value)
try:
netloc_ace = splitted[1].encode('idna') # IDN -> ACE
except UnicodeError: # invalid domain part
raise e
value = value.replace(splitted[1], netloc_ace)
# If no URL path given, assume /
if not splitted[2]:
value += u'/'
super(URLValidator, self).__call__(value)
# After validation revert ACE encoded domain-part to
# original (IDN) value as suggested by RFC 3490
value = original
else:
raise
if self.verify_exists: if self.verify_exists:
import urllib2 import urllib2
headers = { headers = {
@ -77,12 +100,29 @@ def validate_integer(value):
except (ValueError, TypeError), e: except (ValueError, TypeError), e:
raise ValidationError('') raise ValidationError('')
class EmailValidator(RegexValidator):
def __call__(self, value):
try:
super(EmailValidator, self).__call__(value)
except ValidationError, e:
# Trivial case failed. Try for possible IDN domain-part
if value and u'@' in value:
parts = value.split(u'@')
domain_part = parts[-1]
try:
parts[-1] = parts[-1].encode('idna')
except UnicodeError:
raise e
super(EmailValidator, self).__call__(u'@'.join(parts))
else:
raise
email_re = re.compile( email_re = re.compile(
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) # domain r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) # domain
validate_email = RegexValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid') validate_email = EmailValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid')
slug_re = re.compile(r'^[-\w]+$') slug_re = re.compile(r'^[-\w]+$')
validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid') validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid')

View File

@ -482,6 +482,11 @@ Has two optional arguments for validation, ``max_length`` and ``min_length``.
If provided, these arguments ensure that the string is at most or at least the If provided, these arguments ensure that the string is at most or at least the
given length. given length.
.. versionchanged:: 1.2
The EmailField previously did not recognize e-mail addresses as valid that
contained an IDN (Internationalized Domain Name; a domain containing
unicode characters) domain part. This has now been corrected.
``FileField`` ``FileField``
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -707,6 +712,12 @@ Takes the following optional arguments:
String used as the user-agent used when checking for a URL's existence. String used as the user-agent used when checking for a URL's existence.
Defaults to the value of the ``URL_VALIDATOR_USER_AGENT`` setting. Defaults to the value of the ``URL_VALIDATOR_USER_AGENT`` setting.
.. versionchanged:: 1.2
The URLField previously did not recognize URLs as valid that contained an IDN
(Internationalized Domain Name; a domain name containing unicode characters)
domain name. This has now been corrected.
Slightly complex built-in ``Field`` classes Slightly complex built-in ``Field`` classes
------------------------------------------- -------------------------------------------

View File

@ -408,6 +408,8 @@ class FieldsTests(TestCase):
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@inv-.-alid.com') self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@inv-.-alid.com')
self.assertEqual(u'example@valid-----hyphens.com', f.clean('example@valid-----hyphens.com')) self.assertEqual(u'example@valid-----hyphens.com', f.clean('example@valid-----hyphens.com'))
self.assertEqual(u'example@valid-with-hyphens.com', f.clean('example@valid-with-hyphens.com')) self.assertEqual(u'example@valid-with-hyphens.com', f.clean('example@valid-with-hyphens.com'))
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@.com')
self.assertEqual(u'local@domain.with.idn.xyz\xe4\xf6\xfc\xdfabc.part.com', f.clean('local@domain.with.idn.xyzäöüßabc.part.com'))
def test_email_regexp_for_performance(self): def test_email_regexp_for_performance(self):
f = EmailField() f = EmailField()
@ -489,6 +491,7 @@ class FieldsTests(TestCase):
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.alid-.com') self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.alid-.com')
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.-alid.com') self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.-alid.com')
self.assertEqual(u'http://valid-----hyphens.com/', f.clean('http://valid-----hyphens.com')) self.assertEqual(u'http://valid-----hyphens.com/', f.clean('http://valid-----hyphens.com'))
self.assertEqual(u'http://some.idn.xyz\xe4\xf6\xfc\xdfabc.domain.com:123/blah', f.clean('http://some.idn.xyzäöüßabc.domain.com:123/blah'))
def test_url_regex_ticket11198(self): def test_url_regex_ticket11198(self):
f = URLField() f = URLField()