diff --git a/docs/project/changelog.rst b/docs/project/changelog.rst index 738288e43..3e147f850 100644 --- a/docs/project/changelog.rst +++ b/docs/project/changelog.rst @@ -44,6 +44,12 @@ Improvements * :func:`~asyncio.client.connect` now closes connections with close code 1011 (internal error) when exiting the context manager with an exception. +Bug fixes +......... + +* Fixed a regression from 16.1 where the legacy implementation rejected + non-ASCII headers. + .. _17.1: 17.1 diff --git a/src/websockets/legacy/http.py b/src/websockets/legacy/http.py index a7c8a927e..bb7677ac7 100644 --- a/src/websockets/legacy/http.py +++ b/src/websockets/legacy/http.py @@ -175,7 +175,9 @@ async def read_headers(stream: asyncio.StreamReader) -> Headers: name = raw_name.decode("ascii") # guaranteed to be ASCII at this point value = raw_value.decode("ascii", "surrogateescape") - headers[name] = value + + # Since we just validated raw_value, we don't need to revalidate it. + headers.set_insecure(name, value) else: raise SecurityError("too many HTTP headers") diff --git a/src/websockets/sync/server.py b/src/websockets/sync/server.py index d9ffac451..8cb797512 100644 --- a/src/websockets/sync/server.py +++ b/src/websockets/sync/server.py @@ -544,8 +544,8 @@ def handler(websocket): with serve(handler, ...) as server: server.serve_forever() - To stop the server gracefully, call its :meth:`~Server.shutdown` method - from another thread. + To stop the server gracefully, call its :meth:`~Server.shutdown` method from + another thread. Args: handler: Connection handler. It receives the WebSocket connection, diff --git a/tests/legacy/test_http.py b/tests/legacy/test_http.py index 76af61122..b889ab0d6 100644 --- a/tests/legacy/test_http.py +++ b/tests/legacy/test_http.py @@ -152,12 +152,18 @@ async def test_read_response_invalid_header(self): "invalid HTTP header line: Oops", ) - async def test_header_name(self): + async def test_iso_8859_1_header_value(self): + self.stream.feed_data(b"X-Drink: caf\xe9\r\n\r\n") + headers = await read_headers(self.stream) + # Non-ASCII characters are represented with surrogate escapes. + self.assertEqual(headers["X-Drink"], "caf\udce9") + + async def test_invalid_header_name(self): self.stream.feed_data(b"foo bar: baz qux\r\n\r\n") with self.assertRaises(ValueError): await read_headers(self.stream) - async def test_header_value(self): + async def test_invalid_header_value(self): self.stream.feed_data(b"foo: \x00\x00\x0f\r\n\r\n") with self.assertRaises(ValueError): await read_headers(self.stream)