diff --git a/django/http/response.py b/django/http/response.py index 17b8da4da179..12cf47b1c14d 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -32,6 +32,7 @@ _charset_from_content_type_re = _lazy_re_compile( r";\s*charset=(?P[^\s;]+)", re.I ) +_control_chars_re = _lazy_re_compile(r"[\x00-\x1f\x7f-\x9f]") class ResponseHeaders(CaseInsensitiveMapping): @@ -142,7 +143,7 @@ def __init__( if not 100 <= self.status_code <= 599: raise ValueError("HTTP status code must be an integer from 100 to 599.") - self._reason_phrase = reason + self.reason_phrase = reason @property def reason_phrase(self): @@ -154,6 +155,8 @@ def reason_phrase(self): @reason_phrase.setter def reason_phrase(self, value): + if value and _control_chars_re.search(value): + raise BadHeaderError("reason_phrase can't contain control characters.") self._reason_phrase = value @property diff --git a/tests/responses/tests.py b/tests/responses/tests.py index b16cb533648e..05df257b3e6f 100644 --- a/tests/responses/tests.py +++ b/tests/responses/tests.py @@ -2,7 +2,7 @@ from django.conf import settings from django.core.cache import cache -from django.http import HttpResponse +from django.http import BadHeaderError, HttpResponse from django.http.response import HttpResponseBase from django.test import SimpleTestCase @@ -103,6 +103,20 @@ def test_reason_phrase(self): self.assertEqual(resp.status_code, 419) self.assertEqual(resp.reason_phrase, reason) + def test_invalid_reason_phrase(self): + msg = "reason_phrase can't contain control characters." + invalid_reasons = [ + "OK\r\nX-Injected-header: yes", + "OK\x00", + "OK\x1f", + "OK\x7f", + "OK\x9f", + ] + for reason in invalid_reasons: + with self.subTest(reason=reason): + with self.assertRaisesMessage(BadHeaderError, msg): + HttpResponse(reason=reason) + def test_charset_detection(self): """HttpResponse should parse charset from content_type.""" response = HttpResponse("ok")