summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2013-12-21 00:15:39 +0100
committerClaude Paroz <claude@2xlibre.net>2013-12-28 21:25:32 +0100
commit6d66ba59488bbd0d4f0c2f32b2921f1512301143 (patch)
tree5456bc8cee41877c0340cf4da1151e2ee5bf7695
parent9f13c3328199d2fa70235cdc63bb06b1efc5b117 (diff)
Fixed #21242 -- Allowed more IANA schemes in URLValidator
Thanks Sascha Peilicke for the report and initial patch, and Tim Graham for the review.
-rw-r--r--django/core/validators.py16
-rw-r--r--docs/ref/validators.txt18
-rw-r--r--docs/releases/1.7.txt7
-rw-r--r--tests/validators/tests.py7
4 files changed, 44 insertions, 4 deletions
diff --git a/django/core/validators.py b/django/core/validators.py
index 695dda258b..5559bca28f 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -44,7 +44,7 @@ class RegexValidator(object):
@deconstructible
class URLValidator(RegexValidator):
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'localhost|' # localhost...
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'(?:/?|[/?]\S+)$', re.IGNORECASE)
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):
+ 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:
super(URLValidator, self).__call__(value)
except ValidationError as e:
# Trivial case failed. Try for possible IDN domain
if value:
- value = force_text(value)
scheme, netloc, path, query, fragment = urlsplit(value)
try:
netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt
index 92e257ca85..1b706ab8e3 100644
--- a/docs/ref/validators.txt
+++ b/docs/ref/validators.txt
@@ -87,10 +87,24 @@ to, or in lieu of custom ``field.clean()`` methods.
``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
- 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``
------------------
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index 0d83c3aa81..a7e7da6d15 100644
--- a/docs/releases/1.7.txt
+++ b/docs/releases/1.7.txt
@@ -567,6 +567,13 @@ Tests
* :meth:`~django.test.TransactionTestCase.assertNumQueries` now prints
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
=====================================
diff --git a/tests/validators/tests.py b/tests/validators/tests.py
index b0d1376aee..23fa1e3830 100644
--- a/tests/validators/tests.py
+++ b/tests/validators/tests.py
@@ -18,6 +18,7 @@ from django.test.utils import str_prefix
NOW = datetime.now()
+EXTENDED_SCHEMES = ['http', 'https', 'ftp', 'ftps', 'git', 'file']
TEST_DATA = (
# (validator, value, expected),
@@ -141,6 +142,7 @@ TEST_DATA = (
(MinLengthValidator(10), '', ValidationError),
(URLValidator(), 'http://www.djangoproject.com/', None),
+ (URLValidator(), 'HTTP://WWW.DJANGOPROJECT.COM/', None),
(URLValidator(), 'http://localhost/', None),
(URLValidator(), 'http://example.com/', None),
(URLValidator(), 'http://www.example.com/', None),
@@ -155,6 +157,8 @@ TEST_DATA = (
(URLValidator(), 'https://example.com/', None),
(URLValidator(), 'ftp://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(), 'http://', ValidationError),
@@ -165,6 +169,9 @@ TEST_DATA = (
(URLValidator(), 'http://-invalid.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), False, ValidationError),