Skip to content

Commit 1851048

Browse files
Restrict reason (#12209)
Co-authored-by: Dhiral Vyas <dhiral@users.noreply.github.com>
1 parent 8b68524 commit 1851048

File tree

6 files changed

+37
-7
lines changed

6 files changed

+37
-7
lines changed

aiohttp/_http_writer.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def _serialize_headers(str status_line, headers):
131131
_init_writer(&writer, buf)
132132

133133
try:
134-
if _write_str(&writer, status_line) < 0:
134+
if _write_str_raise_on_nlcr(&writer, status_line) < 0:
135135
raise
136136
if _write_byte(&writer, b'\r') < 0:
137137
raise

aiohttp/http_writer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ def _safe_header(string: str) -> str:
373373

374374

375375
def _py_serialize_headers(status_line: str, headers: "CIMultiDict[str]") -> bytes:
376+
_safe_header(status_line)
376377
headers_gen = (_safe_header(k) + ": " + _safe_header(v) for k, v in headers.items())
377378
line = status_line + "\r\n" + "\r\n".join(headers_gen) + "\r\n\r\n"
378379
return line.encode("utf-8")

aiohttp/web_exceptions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ def __init__(
9999
) -> None:
100100
if reason is None:
101101
reason = self.default_reason
102-
elif "\n" in reason:
103-
raise ValueError("Reason cannot contain \\n")
102+
elif "\r" in reason or "\n" in reason:
103+
raise ValueError("Reason cannot contain \\r or \\n")
104104

105105
if text is None:
106106
if not self.empty_body:

aiohttp/web_response.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ def _set_status(self, status: int, reason: str | None) -> None:
157157
self._status = status
158158
if reason is None:
159159
reason = REASON_PHRASES.get(self._status, "")
160-
elif "\n" in reason:
161-
raise ValueError("Reason cannot contain \\n")
160+
elif "\r" in reason or "\n" in reason:
161+
raise ValueError("Reason cannot contain \\r or \\n")
162162
self._reason = reason
163163

164164
@property

tests/test_web_exceptions.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,17 @@ def test_ctor_all(self) -> None:
185185
assert resp.status == 200
186186

187187
def test_multiline_reason(self) -> None:
188-
with pytest.raises(ValueError, match=r"Reason cannot contain \\n"):
188+
with pytest.raises(ValueError, match=r"Reason cannot contain"):
189189
web.HTTPOk(reason="Bad\r\nInjected-header: foo")
190190

191+
def test_reason_with_cr(self) -> None:
192+
with pytest.raises(ValueError, match=r"Reason cannot contain"):
193+
web.HTTPOk(reason="OK\rSet-Cookie: evil=1")
194+
195+
def test_reason_with_lf(self) -> None:
196+
with pytest.raises(ValueError, match=r"Reason cannot contain"):
197+
web.HTTPOk(reason="OK\nSet-Cookie: evil=1")
198+
191199
def test_pickle(self) -> None:
192200
resp = web.HTTPOk(
193201
headers={"X-Custom": "value"},

tests/test_web_response.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,27 @@ def test_set_status_with_empty_reason() -> None:
934934
assert resp.reason == ""
935935

936936

937+
def test_set_status_reason_with_cr() -> None:
938+
resp = web.StreamResponse()
939+
940+
with pytest.raises(ValueError, match="Reason cannot contain"):
941+
resp.set_status(200, "OK\rSet-Cookie: evil=1")
942+
943+
944+
def test_set_status_reason_with_lf() -> None:
945+
resp = web.StreamResponse()
946+
947+
with pytest.raises(ValueError, match="Reason cannot contain"):
948+
resp.set_status(200, "OK\nSet-Cookie: evil=1")
949+
950+
951+
def test_set_status_reason_with_crlf() -> None:
952+
resp = web.StreamResponse()
953+
954+
with pytest.raises(ValueError, match="Reason cannot contain"):
955+
resp.set_status(200, "OK\r\nSet-Cookie: evil=1")
956+
957+
937958
async def test_start_force_close() -> None:
938959
req = make_request("GET", "/")
939960
resp = web.StreamResponse()
@@ -1236,7 +1257,7 @@ async def test_render_with_body(buf: bytearray, writer: AbstractStreamWriter) ->
12361257

12371258

12381259
async def test_multiline_reason(buf: bytearray, writer: AbstractStreamWriter) -> None:
1239-
with pytest.raises(ValueError, match=r"Reason cannot contain \\n"):
1260+
with pytest.raises(ValueError, match=r"Reason cannot contain \\r or \\n"):
12401261
web.Response(reason="Bad\r\nInjected-header: foo")
12411262

12421263

0 commit comments

Comments
 (0)