diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 8d25d6c78..1da58d2ee 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -153,6 +153,7 @@ Corresponds to `django.db.models.fields.CharField` or `django.db.models.fields.T - `max_length` - Validates that the input contains no more than this number of characters. - `min_length` - Validates that the input contains no fewer than this number of characters. - `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. +- `allow_null_bytes` - If set to `False`, strings containing NULL bytes will be rejected. You may want to set this to `False` if the string is saved to a database. Defaults to `True`. - `trim_whitespace` - If set to `True` then leading and trailing whitespace is trimmed. Defaults to `True`. The `allow_null` option is also available for string fields, although its usage is discouraged in favor of `allow_blank`. It is valid to set both `allow_blank=True` and `allow_null=True`, but doing so means that there will be two differing types of empty value permissible for string representations, which can lead to data inconsistencies and subtle application bugs. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 3278cf51c..fa6771f45 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -754,12 +754,14 @@ class CharField(Field): 'invalid': _('Not a valid string.'), 'blank': _('This field may not be blank.'), 'max_length': _('Ensure this field has no more than {max_length} characters.'), - 'min_length': _('Ensure this field has at least {min_length} characters.') + 'min_length': _('Ensure this field has at least {min_length} characters.'), + 'nulls': _('This field may not include NULL bytes.'), } initial = '' def __init__(self, **kwargs): self.allow_blank = kwargs.pop('allow_blank', False) + self.allow_null_bytes = kwargs.pop('allow_null_bytes', True) self.trim_whitespace = kwargs.pop('trim_whitespace', True) self.max_length = kwargs.pop('max_length', None) self.min_length = kwargs.pop('min_length', None) @@ -785,6 +787,8 @@ class CharField(Field): if not self.allow_blank: self.fail('blank') return '' + if not self.allow_null_bytes and '\0' in six.text_type(data): + self.fail('nulls') return super(CharField, self).run_validation(data) def to_internal_value(self, data): diff --git a/tests/test_fields.py b/tests/test_fields.py index aa3391a72..067a2e4b4 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -718,6 +718,21 @@ class TestCharField(FieldValues): field.run_validation(' ') assert exc_info.value.detail == ['This field may not be blank.'] + def test_allow_null_bytes(self): + field = serializers.CharField(allow_null_bytes=True) + for value in ('\0', 'foo\0', '\0foo', 'foo\0foo'): + field.run_validation(value) + + def test_disallow_null_bytes(self): + field = serializers.CharField(allow_null_bytes=False) + + for value in ('\0', 'foo\0', '\0foo', 'foo\0foo'): + with pytest.raises(serializers.ValidationError) as exc_info: + field.run_validation(value) + assert exc_info.value.detail == [ + serializers.CharField.default_error_messages['nulls'] + ] + class TestEmailField(FieldValues): """