mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #28201 -- Added ProhibitNullCharactersValidator and used it on CharField form field.
This commit is contained in:
		
				
					committed by
					
						
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							b78d100fa6
						
					
				
				
					commit
					90d7b912b9
				
			@@ -503,3 +503,27 @@ def get_available_image_extensions():
 | 
				
			|||||||
validate_image_file_extension = FileExtensionValidator(
 | 
					validate_image_file_extension = FileExtensionValidator(
 | 
				
			||||||
    allowed_extensions=get_available_image_extensions(),
 | 
					    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
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -217,6 +217,7 @@ class CharField(Field):
 | 
				
			|||||||
            self.validators.append(validators.MinLengthValidator(int(min_length)))
 | 
					            self.validators.append(validators.MinLengthValidator(int(min_length)))
 | 
				
			||||||
        if max_length is not None:
 | 
					        if max_length is not None:
 | 
				
			||||||
            self.validators.append(validators.MaxLengthValidator(int(max_length)))
 | 
					            self.validators.append(validators.MaxLengthValidator(int(max_length)))
 | 
				
			||||||
 | 
					        self.validators.append(validators.ProhibitNullCharactersValidator())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def to_python(self, value):
 | 
					    def to_python(self, value):
 | 
				
			||||||
        """Return a string."""
 | 
					        """Return a string."""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -304,3 +304,27 @@ to, or in lieu of custom ``field.clean()`` methods.
 | 
				
			|||||||
    Uses Pillow to ensure that ``value.name`` (``value`` is a
 | 
					    Uses Pillow to ensure that ``value.name`` (``value`` is a
 | 
				
			||||||
    :class:`~django.core.files.File`) has `a valid image extension
 | 
					    :class:`~django.core.files.File`) has `a valid image extension
 | 
				
			||||||
    <https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html>`_.
 | 
					    <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"``.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -318,7 +318,12 @@ URLs
 | 
				
			|||||||
Validators
 | 
					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:
 | 
					.. _backwards-incompatible-2.0:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -123,3 +123,9 @@ class CharFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
 | 
				
			|||||||
    def test_charfield_disabled(self):
 | 
					    def test_charfield_disabled(self):
 | 
				
			||||||
        f = CharField(disabled=True)
 | 
					        f = CharField(disabled=True)
 | 
				
			||||||
        self.assertWidgetRendersTo(f, '<input type="text" name="f" id="id_f" disabled required />')
 | 
					        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')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,11 +9,11 @@ from django.core.files.base import ContentFile
 | 
				
			|||||||
from django.core.validators import (
 | 
					from django.core.validators import (
 | 
				
			||||||
    BaseValidator, DecimalValidator, EmailValidator, FileExtensionValidator,
 | 
					    BaseValidator, DecimalValidator, EmailValidator, FileExtensionValidator,
 | 
				
			||||||
    MaxLengthValidator, MaxValueValidator, MinLengthValidator,
 | 
					    MaxLengthValidator, MaxValueValidator, MinLengthValidator,
 | 
				
			||||||
    MinValueValidator, RegexValidator, URLValidator, int_list_validator,
 | 
					    MinValueValidator, ProhibitNullCharactersValidator, RegexValidator,
 | 
				
			||||||
    validate_comma_separated_integer_list, validate_email,
 | 
					    URLValidator, int_list_validator, validate_comma_separated_integer_list,
 | 
				
			||||||
    validate_image_file_extension, validate_integer, validate_ipv4_address,
 | 
					    validate_email, validate_image_file_extension, validate_integer,
 | 
				
			||||||
    validate_ipv6_address, validate_ipv46_address, validate_slug,
 | 
					    validate_ipv4_address, validate_ipv6_address, validate_ipv46_address,
 | 
				
			||||||
    validate_unicode_slug,
 | 
					    validate_slug, validate_unicode_slug,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from django.test import SimpleTestCase
 | 
					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.PNG'), None),
 | 
				
			||||||
    (validate_image_file_extension, ContentFile('contents', name='file.txt'), ValidationError),
 | 
					    (validate_image_file_extension, ContentFile('contents', name='file.txt'), ValidationError),
 | 
				
			||||||
    (validate_image_file_extension, ContentFile('contents', name='file'), 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']),
 | 
				
			||||||
            FileExtensionValidator(['txt'], message='custom error message')
 | 
					            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')
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user