Fixed #21242 -- Allowed more IANA schemes in URLValidator
Thanks Sascha Peilicke for the report and initial patch, and Tim Graham for the review.
This commit is contained in:
parent
9f13c33281
commit
6d66ba5948
|
@ -44,7 +44,7 @@ class RegexValidator(object):
|
||||||
@deconstructible
|
@deconstructible
|
||||||
class URLValidator(RegexValidator):
|
class URLValidator(RegexValidator):
|
||||||
regex = re.compile(
|
regex = re.compile(
|
||||||
r'^(?:http|ftp)s?://' # http:// or https://
|
r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately
|
||||||
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
|
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
|
||||||
r'localhost|' # localhost...
|
r'localhost|' # localhost...
|
||||||
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
|
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
|
||||||
|
@ -52,14 +52,26 @@ class URLValidator(RegexValidator):
|
||||||
r'(?::\d+)?' # optional port
|
r'(?::\d+)?' # optional port
|
||||||
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
||||||
message = _('Enter a valid URL.')
|
message = _('Enter a valid URL.')
|
||||||
|
schemes = ['http', 'https', 'ftp', 'ftps']
|
||||||
|
|
||||||
|
def __init__(self, schemes=None, **kwargs):
|
||||||
|
super(URLValidator, self).__init__(**kwargs)
|
||||||
|
if schemes is not None:
|
||||||
|
self.schemes = schemes
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
|
value = force_text(value)
|
||||||
|
# Check first if the scheme is valid
|
||||||
|
scheme = value.split('://')[0].lower()
|
||||||
|
if scheme not in self.schemes:
|
||||||
|
raise ValidationError(self.message, code=self.code)
|
||||||
|
|
||||||
|
# Then check full URL
|
||||||
try:
|
try:
|
||||||
super(URLValidator, self).__call__(value)
|
super(URLValidator, self).__call__(value)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
# Trivial case failed. Try for possible IDN domain
|
# Trivial case failed. Try for possible IDN domain
|
||||||
if value:
|
if value:
|
||||||
value = force_text(value)
|
|
||||||
scheme, netloc, path, query, fragment = urlsplit(value)
|
scheme, netloc, path, query, fragment = urlsplit(value)
|
||||||
try:
|
try:
|
||||||
netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
|
netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
|
||||||
|
|
|
@ -87,10 +87,24 @@ to, or in lieu of custom ``field.clean()`` methods.
|
||||||
|
|
||||||
``URLValidator``
|
``URLValidator``
|
||||||
----------------
|
----------------
|
||||||
.. class:: URLValidator()
|
.. class:: URLValidator([schemes=None, regex=None, message=None, code=None])
|
||||||
|
|
||||||
A :class:`RegexValidator` that ensures a value looks like a URL, and raises
|
A :class:`RegexValidator` that ensures a value looks like a URL, and raises
|
||||||
an error code of ``'invalid'`` if it doesn't.
|
an error code of ``'invalid'`` if it doesn't. In addition to the optional
|
||||||
|
arguments of its parent :class:`RegexValidator` class, ``URLValidator``
|
||||||
|
accepts an extra optional attribute:
|
||||||
|
|
||||||
|
.. attribute:: schemes
|
||||||
|
|
||||||
|
URL/URI scheme list to validate against. If not provided, the default
|
||||||
|
list is ``['http', 'https', 'ftp', 'ftps']``. As a reference, the IANA
|
||||||
|
Web site provides a full list of `valid URI schemes`_.
|
||||||
|
|
||||||
|
.. _valid URI schemes: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
The optional ``schemes`` attribute was added.
|
||||||
|
|
||||||
``validate_email``
|
``validate_email``
|
||||||
------------------
|
------------------
|
||||||
|
|
|
@ -567,6 +567,13 @@ Tests
|
||||||
* :meth:`~django.test.TransactionTestCase.assertNumQueries` now prints
|
* :meth:`~django.test.TransactionTestCase.assertNumQueries` now prints
|
||||||
out the list of executed queries if the assertion fails.
|
out the list of executed queries if the assertion fails.
|
||||||
|
|
||||||
|
Validators
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
* :class:`~django.core.validators.URLValidator` now accepts an optional
|
||||||
|
``schemes`` argument which allows customization of the accepted URI schemes
|
||||||
|
(instead of the defaults ``http(s)`` and ``ftp(s)``).
|
||||||
|
|
||||||
Backwards incompatible changes in 1.7
|
Backwards incompatible changes in 1.7
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ from django.test.utils import str_prefix
|
||||||
|
|
||||||
|
|
||||||
NOW = datetime.now()
|
NOW = datetime.now()
|
||||||
|
EXTENDED_SCHEMES = ['http', 'https', 'ftp', 'ftps', 'git', 'file']
|
||||||
|
|
||||||
TEST_DATA = (
|
TEST_DATA = (
|
||||||
# (validator, value, expected),
|
# (validator, value, expected),
|
||||||
|
@ -141,6 +142,7 @@ TEST_DATA = (
|
||||||
(MinLengthValidator(10), '', ValidationError),
|
(MinLengthValidator(10), '', ValidationError),
|
||||||
|
|
||||||
(URLValidator(), 'http://www.djangoproject.com/', None),
|
(URLValidator(), 'http://www.djangoproject.com/', None),
|
||||||
|
(URLValidator(), 'HTTP://WWW.DJANGOPROJECT.COM/', None),
|
||||||
(URLValidator(), 'http://localhost/', None),
|
(URLValidator(), 'http://localhost/', None),
|
||||||
(URLValidator(), 'http://example.com/', None),
|
(URLValidator(), 'http://example.com/', None),
|
||||||
(URLValidator(), 'http://www.example.com/', None),
|
(URLValidator(), 'http://www.example.com/', None),
|
||||||
|
@ -155,6 +157,8 @@ TEST_DATA = (
|
||||||
(URLValidator(), 'https://example.com/', None),
|
(URLValidator(), 'https://example.com/', None),
|
||||||
(URLValidator(), 'ftp://example.com/', None),
|
(URLValidator(), 'ftp://example.com/', None),
|
||||||
(URLValidator(), 'ftps://example.com/', None),
|
(URLValidator(), 'ftps://example.com/', None),
|
||||||
|
(URLValidator(EXTENDED_SCHEMES), 'file://localhost/path', None),
|
||||||
|
(URLValidator(EXTENDED_SCHEMES), 'git://example.com/', None),
|
||||||
|
|
||||||
(URLValidator(), 'foo', ValidationError),
|
(URLValidator(), 'foo', ValidationError),
|
||||||
(URLValidator(), 'http://', ValidationError),
|
(URLValidator(), 'http://', ValidationError),
|
||||||
|
@ -165,6 +169,9 @@ TEST_DATA = (
|
||||||
(URLValidator(), 'http://-invalid.com', ValidationError),
|
(URLValidator(), 'http://-invalid.com', ValidationError),
|
||||||
(URLValidator(), 'http://inv-.alid-.com', ValidationError),
|
(URLValidator(), 'http://inv-.alid-.com', ValidationError),
|
||||||
(URLValidator(), 'http://inv-.-alid.com', ValidationError),
|
(URLValidator(), 'http://inv-.-alid.com', ValidationError),
|
||||||
|
(URLValidator(), 'file://localhost/path', ValidationError),
|
||||||
|
(URLValidator(), 'git://example.com/', ValidationError),
|
||||||
|
(URLValidator(EXTENDED_SCHEMES), 'git://-invalid.com', ValidationError),
|
||||||
|
|
||||||
(BaseValidator(True), True, None),
|
(BaseValidator(True), True, None),
|
||||||
(BaseValidator(True), False, ValidationError),
|
(BaseValidator(True), False, ValidationError),
|
||||||
|
|
Loading…
Reference in New Issue