Skip to content

Commit 90d7b91

Browse files
Alejandro Zamoratimgraham
authored andcommitted
Fixed #28201 -- Added ProhibitNullCharactersValidator and used it on CharField form field.
1 parent b78d100 commit 90d7b91

File tree

6 files changed

+88
-6
lines changed

6 files changed

+88
-6
lines changed

django/core/validators.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,3 +503,27 @@ def get_available_image_extensions():
503503
validate_image_file_extension = FileExtensionValidator(
504504
allowed_extensions=get_available_image_extensions(),
505505
)
506+
507+
508+
@deconstructible
509+
class ProhibitNullCharactersValidator:
510+
"""Validate that the string doesn't contain the null character."""
511+
message = _('Null characters are not allowed.')
512+
code = 'null_characters_not_allowed'
513+
514+
def __init__(self, message=None, code=None):
515+
if message is not None:
516+
self.message = message
517+
if code is not None:
518+
self.code = code
519+
520+
def __call__(self, value):
521+
if '\x00' in str(value):
522+
raise ValidationError(self.message, code=self.code)
523+
524+
def __eq__(self, other):
525+
return (
526+
isinstance(other, self.__class__) and
527+
self.message == other.message and
528+
self.code == other.code
529+
)

django/forms/fields.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ def __init__(self, *, max_length=None, min_length=None, strip=True, empty_value=
217217
self.validators.append(validators.MinLengthValidator(int(min_length)))
218218
if max_length is not None:
219219
self.validators.append(validators.MaxLengthValidator(int(max_length)))
220+
self.validators.append(validators.ProhibitNullCharactersValidator())
220221

221222
def to_python(self, value):
222223
"""Return a string."""

docs/ref/validators.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,27 @@ to, or in lieu of custom ``field.clean()`` methods.
304304
Uses Pillow to ensure that ``value.name`` (``value`` is a
305305
:class:`~django.core.files.File`) has `a valid image extension
306306
<https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html>`_.
307+
308+
``ProhibitNullCharactersValidator``
309+
-----------------------------------
310+
311+
.. class:: ProhibitNullCharactersValidator(message=None, code=None)
312+
313+
.. versionadded:: 2.0
314+
315+
Raises a :exc:`~django.core.exceptions.ValidationError` if ``str(value)``
316+
contains one or more nulls characters (``'\x00'``).
317+
318+
:param message: If not ``None``, overrides :attr:`.message`.
319+
:param code: If not ``None``, overrides :attr:`code`.
320+
321+
.. attribute:: message
322+
323+
The error message used by
324+
:exc:`~django.core.exceptions.ValidationError` if validation fails.
325+
Defaults to ``"Null characters are not allowed."``.
326+
327+
.. attribute:: code
328+
329+
The error code used by :exc:`~django.core.exceptions.ValidationError`
330+
if validation fails. Defaults to ``"null_characters_not_allowed"``.

docs/releases/2.0.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,12 @@ URLs
318318
Validators
319319
~~~~~~~~~~
320320

321-
* ...
321+
* The new :class:`.ProhibitNullCharactersValidator` disallows the null
322+
character in the input of the :class:`~django.forms.CharField` form field
323+
and its subclasses. Null character input was observed from vulnerability
324+
scanning tools. Most databases silently discard null characters, but
325+
psycopg2 2.7+ raises an exception when trying to save a null character to
326+
a char/text field with PostgreSQL.
322327

323328
.. _backwards-incompatible-2.0:
324329

tests/forms_tests/field_tests/test_charfield.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,9 @@ def test_charfield_strip(self):
123123
def test_charfield_disabled(self):
124124
f = CharField(disabled=True)
125125
self.assertWidgetRendersTo(f, '<input type="text" name="f" id="id_f" disabled required />')
126+
127+
def test_null_characters_prohibited(self):
128+
f = CharField()
129+
msg = 'Null characters are not allowed.'
130+
with self.assertRaisesMessage(ValidationError, msg):
131+
f.clean('\x00something')

tests/validators/tests.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
from django.core.validators import (
1010
BaseValidator, DecimalValidator, EmailValidator, FileExtensionValidator,
1111
MaxLengthValidator, MaxValueValidator, MinLengthValidator,
12-
MinValueValidator, RegexValidator, URLValidator, int_list_validator,
13-
validate_comma_separated_integer_list, validate_email,
14-
validate_image_file_extension, validate_integer, validate_ipv4_address,
15-
validate_ipv6_address, validate_ipv46_address, validate_slug,
16-
validate_unicode_slug,
12+
MinValueValidator, ProhibitNullCharactersValidator, RegexValidator,
13+
URLValidator, int_list_validator, validate_comma_separated_integer_list,
14+
validate_email, validate_image_file_extension, validate_integer,
15+
validate_ipv4_address, validate_ipv6_address, validate_ipv46_address,
16+
validate_slug, validate_unicode_slug,
1717
)
1818
from django.test import SimpleTestCase
1919

@@ -264,6 +264,10 @@
264264
(validate_image_file_extension, ContentFile('contents', name='file.PNG'), None),
265265
(validate_image_file_extension, ContentFile('contents', name='file.txt'), ValidationError),
266266
(validate_image_file_extension, ContentFile('contents', name='file'), ValidationError),
267+
268+
(ProhibitNullCharactersValidator(), '\x00something', ValidationError),
269+
(ProhibitNullCharactersValidator(), 'something', None),
270+
(ProhibitNullCharactersValidator(), None, None),
267271
]
268272

269273

@@ -488,3 +492,21 @@ def test_file_extension_equality(self):
488492
FileExtensionValidator(['txt']),
489493
FileExtensionValidator(['txt'], message='custom error message')
490494
)
495+
496+
def test_prohibit_null_characters_validator_equality(self):
497+
self.assertEqual(
498+
ProhibitNullCharactersValidator(message='message', code='code'),
499+
ProhibitNullCharactersValidator(message='message', code='code')
500+
)
501+
self.assertEqual(
502+
ProhibitNullCharactersValidator(),
503+
ProhibitNullCharactersValidator()
504+
)
505+
self.assertNotEqual(
506+
ProhibitNullCharactersValidator(message='message1', code='code'),
507+
ProhibitNullCharactersValidator(message='message2', code='code')
508+
)
509+
self.assertNotEqual(
510+
ProhibitNullCharactersValidator(message='message', code='code1'),
511+
ProhibitNullCharactersValidator(message='message', code='code2')
512+
)

0 commit comments

Comments
 (0)