فهرست منبع

Fixed #28201 -- Added ProhibitNullCharactersValidator and used it on CharField form field.

Alejandro Zamora 7 سال پیش
والد
کامیت
90d7b912b9

+ 24 - 0
django/core/validators.py

@@ -503,3 +503,27 @@ def get_available_image_extensions():
 validate_image_file_extension = FileExtensionValidator(
     allowed_extensions=get_available_image_extensions(),
 )
+
+
+@deconstructible
+class ProhibitNullCharactersValidator:
+    """Validate that the string doesn't contain the null character."""
+    message = _('Null characters are not allowed.')
+    code = 'null_characters_not_allowed'
+
+    def __init__(self, message=None, code=None):
+        if message is not None:
+            self.message = message
+        if code is not None:
+            self.code = code
+
+    def __call__(self, value):
+        if '\x00' in str(value):
+            raise ValidationError(self.message, code=self.code)
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, self.__class__) and
+            self.message == other.message and
+            self.code == other.code
+        )

+ 1 - 0
django/forms/fields.py

@@ -217,6 +217,7 @@ class CharField(Field):
             self.validators.append(validators.MinLengthValidator(int(min_length)))
         if max_length is not None:
             self.validators.append(validators.MaxLengthValidator(int(max_length)))
+        self.validators.append(validators.ProhibitNullCharactersValidator())
 
     def to_python(self, value):
         """Return a string."""

+ 24 - 0
docs/ref/validators.txt

@@ -304,3 +304,27 @@ to, or in lieu of custom ``field.clean()`` methods.
     Uses Pillow to ensure that ``value.name`` (``value`` is a
     :class:`~django.core.files.File`) has `a valid image extension
     <https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html>`_.
+
+``ProhibitNullCharactersValidator``
+-----------------------------------
+
+.. class:: ProhibitNullCharactersValidator(message=None, code=None)
+
+    .. versionadded:: 2.0
+
+    Raises a :exc:`~django.core.exceptions.ValidationError` if ``str(value)``
+    contains one or more nulls characters (``'\x00'``).
+
+    :param message: If not ``None``, overrides :attr:`.message`.
+    :param code: If not ``None``, overrides :attr:`code`.
+
+    .. attribute:: message
+
+        The error message used by
+        :exc:`~django.core.exceptions.ValidationError` if validation fails.
+        Defaults to ``"Null characters are not allowed."``.
+
+    .. attribute:: code
+
+        The error code used by :exc:`~django.core.exceptions.ValidationError`
+        if validation fails. Defaults to ``"null_characters_not_allowed"``.

+ 6 - 1
docs/releases/2.0.txt

@@ -318,7 +318,12 @@ URLs
 Validators
 ~~~~~~~~~~
 
-* ...
+* The new :class:`.ProhibitNullCharactersValidator` disallows the null
+  character in the input of the :class:`~django.forms.CharField` form field
+  and its subclasses. Null character input was observed from vulnerability
+  scanning tools. Most databases silently discard null characters, but
+  psycopg2 2.7+ raises an exception when trying to save a null character to
+  a char/text field with PostgreSQL.
 
 .. _backwards-incompatible-2.0:
 

+ 6 - 0
tests/forms_tests/field_tests/test_charfield.py

@@ -123,3 +123,9 @@ class CharFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
     def test_charfield_disabled(self):
         f = CharField(disabled=True)
         self.assertWidgetRendersTo(f, '<input type="text" name="f" id="id_f" disabled required />')
+
+    def test_null_characters_prohibited(self):
+        f = CharField()
+        msg = 'Null characters are not allowed.'
+        with self.assertRaisesMessage(ValidationError, msg):
+            f.clean('\x00something')

+ 27 - 5
tests/validators/tests.py

@@ -9,11 +9,11 @@ from django.core.files.base import ContentFile
 from django.core.validators import (
     BaseValidator, DecimalValidator, EmailValidator, FileExtensionValidator,
     MaxLengthValidator, MaxValueValidator, MinLengthValidator,
-    MinValueValidator, RegexValidator, URLValidator, int_list_validator,
-    validate_comma_separated_integer_list, validate_email,
-    validate_image_file_extension, validate_integer, validate_ipv4_address,
-    validate_ipv6_address, validate_ipv46_address, validate_slug,
-    validate_unicode_slug,
+    MinValueValidator, ProhibitNullCharactersValidator, RegexValidator,
+    URLValidator, int_list_validator, validate_comma_separated_integer_list,
+    validate_email, validate_image_file_extension, validate_integer,
+    validate_ipv4_address, validate_ipv6_address, validate_ipv46_address,
+    validate_slug, validate_unicode_slug,
 )
 from django.test import SimpleTestCase
 
@@ -264,6 +264,10 @@ TEST_DATA = [
     (validate_image_file_extension, ContentFile('contents', name='file.PNG'), None),
     (validate_image_file_extension, ContentFile('contents', name='file.txt'), ValidationError),
     (validate_image_file_extension, ContentFile('contents', name='file'), ValidationError),
+
+    (ProhibitNullCharactersValidator(), '\x00something', ValidationError),
+    (ProhibitNullCharactersValidator(), 'something', None),
+    (ProhibitNullCharactersValidator(), None, None),
 ]
 
 
@@ -488,3 +492,21 @@ class TestValidatorEquality(TestCase):
             FileExtensionValidator(['txt']),
             FileExtensionValidator(['txt'], message='custom error message')
         )
+
+    def test_prohibit_null_characters_validator_equality(self):
+        self.assertEqual(
+            ProhibitNullCharactersValidator(message='message', code='code'),
+            ProhibitNullCharactersValidator(message='message', code='code')
+        )
+        self.assertEqual(
+            ProhibitNullCharactersValidator(),
+            ProhibitNullCharactersValidator()
+        )
+        self.assertNotEqual(
+            ProhibitNullCharactersValidator(message='message1', code='code'),
+            ProhibitNullCharactersValidator(message='message2', code='code')
+        )
+        self.assertNotEqual(
+            ProhibitNullCharactersValidator(message='message', code='code1'),
+            ProhibitNullCharactersValidator(message='message', code='code2')
+        )