diff --git a/CHANGES/9167.bugfix.rst b/CHANGES/9167.bugfix.rst new file mode 100644 index 0000000000..4c33c8ad35 --- /dev/null +++ b/CHANGES/9167.bugfix.rst @@ -0,0 +1 @@ +Rejected `\n` in `reason` values to avoid sending broken HTTP messages -- by :user:`Dreamsorcerer`. diff --git a/aiohttp/web_response.py b/aiohttp/web_response.py index 71a94eec24..c14a7544d6 100644 --- a/aiohttp/web_response.py +++ b/aiohttp/web_response.py @@ -143,6 +143,8 @@ def set_status( reason = HTTPStatus(self._status).phrase except ValueError: reason = "" + if "\n" in reason: + raise ValueError("Reason cannot contain \\n") self._reason = reason @property diff --git a/tests/test_web_exceptions.py b/tests/test_web_exceptions.py index 69deb27a06..3358a947d3 100644 --- a/tests/test_web_exceptions.py +++ b/tests/test_web_exceptions.py @@ -270,3 +270,8 @@ def test_unicode_text_body_unauthorized() -> None: ): resp = web.HTTPUnauthorized(body="text") assert resp.status == 401 + + +def test_multiline_reason() -> None: + with pytest.raises(ValueError, match=r"Reason cannot contain \\n"): + web.HTTPOk(reason="Bad\r\nInjected-header: foo") diff --git a/tests/test_web_response.py b/tests/test_web_response.py index 36642d3d24..3694e65948 100644 --- a/tests/test_web_response.py +++ b/tests/test_web_response.py @@ -944,14 +944,14 @@ async def test_start_force_close() -> None: async def test___repr__() -> None: req = make_request("GET", "/path/to") - resp = StreamResponse(reason=301) + resp = StreamResponse(reason="foo") await resp.prepare(req) - assert "" == repr(resp) + assert "" == repr(resp) def test___repr___not_prepared() -> None: - resp = StreamResponse(reason=301) - assert "" == repr(resp) + resp = StreamResponse(reason="foo") + assert "" == repr(resp) async def test_keep_alive_http10_default() -> None: @@ -1225,6 +1225,11 @@ async def test_render_with_body(buf, writer) -> None: ) +async def test_multiline_reason(buf, writer) -> None: + with pytest.raises(ValueError, match=r"Reason cannot contain \\n"): + Response(reason="Bad\r\nInjected-header: foo") + + async def test_send_set_cookie_header(buf, writer) -> None: resp = Response() resp.cookies["name"] = "value"