From 373e521f3685c48eb22e6fbb1d7079f161a4a67b Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Mon, 6 Jan 2020 15:12:21 +0100 Subject: [PATCH] Make CharField prohibit surrogate characters (#7026) (#7067) * CharField: Detect and prohibit surrogate characters * CharField: Cover handling of surrogate characters --- rest_framework/fields.py | 2 ++ rest_framework/validators.py | 11 +++++++++++ tests/test_fields.py | 15 +++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 3df7888a0..958bebeef 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -36,6 +36,7 @@ from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, json, representation from rest_framework.utils.formatting import lazy_format +from rest_framework.validators import ProhibitSurrogateCharactersValidator class empty: @@ -818,6 +819,7 @@ class CharField(Field): # ProhibitNullCharactersValidator is None on Django < 2.0 if ProhibitNullCharactersValidator is not None: self.validators.append(ProhibitNullCharactersValidator()) + self.validators.append(ProhibitSurrogateCharactersValidator()) def run_validation(self, data=empty): # Test for the empty string here so that it does not get validated, diff --git a/rest_framework/validators.py b/rest_framework/validators.py index 4681d4fb1..a5cb75a84 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -167,6 +167,17 @@ class UniqueTogetherValidator: ) +class ProhibitSurrogateCharactersValidator: + message = _('Surrogate characters are not allowed: U+{code_point:X}.') + code = 'surrogate_characters_not_allowed' + + def __call__(self, value): + for surrogate_character in (ch for ch in str(value) + if 0xD800 <= ord(ch) <= 0xDFFF): + message = self.message.format(code_point=ord(surrogate_character)) + raise ValidationError(message, code=self.code) + + class BaseUniqueForValidator: message = None missing_message = _('This field is required.') diff --git a/tests/test_fields.py b/tests/test_fields.py index 0be1b1a7a..a4b78fd51 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -758,6 +758,21 @@ class TestCharField(FieldValues): 'Null characters are not allowed.' ] + def test_surrogate_characters(self): + field = serializers.CharField() + + for code_point, expected_message in ( + (0xD800, 'Surrogate characters are not allowed: U+D800.'), + (0xDFFF, 'Surrogate characters are not allowed: U+DFFF.'), + ): + with pytest.raises(serializers.ValidationError) as exc_info: + field.run_validation(chr(code_point)) + assert exc_info.value.detail[0].code == 'surrogate_characters_not_allowed' + assert str(exc_info.value.detail[0]) == expected_message + + for code_point in (0xD800 - 1, 0xDFFF + 1): + field.run_validation(chr(code_point)) + def test_iterable_validators(self): """ Ensure `validators` parameter is compatible with reasonable iterables.