Skip to content

Commit 81bbb32

Browse files
committed
Send a 400 if data is received before the websocket is accepted
This is necessary as the data is not known to be either websocket or http data as the server must accept or reject the connection and hence it is a bad request. In practice this is rare as the upgrade request is a GET request which rarely has body data and most websocket clients wait for acceptance.
1 parent d264794 commit 81bbb32

File tree

2 files changed

+34
-0
lines changed

2 files changed

+34
-0
lines changed

src/hypercorn/protocol/ws_stream.py

+5
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class FrameTooLargeError(Exception):
5656

5757
class Handshake:
5858
def __init__(self, headers: List[Tuple[bytes, bytes]], http_version: str) -> None:
59+
self.accepted = False
5960
self.http_version = http_version
6061
self.connection_tokens: Optional[List[str]] = None
6162
self.extensions: Optional[List[str]] = None
@@ -129,6 +130,7 @@ def accept(
129130

130131
headers.append((name, value))
131132

133+
self.accepted = True
132134
return status_code, headers, Connection(ConnectionType.SERVER, extensions)
133135

134136

@@ -232,6 +234,9 @@ async def handle(self, event: Event) -> None:
232234
self.app, self.config, self.scope, self.app_send
233235
)
234236
await self.app_put({"type": "websocket.connect"})
237+
elif isinstance(event, (Body, Data)) and not self.handshake.accepted:
238+
await self._send_error_response(400)
239+
self.closed = True
235240
elif isinstance(event, (Body, Data)):
236241
self.connection.receive_data(event.data)
237242
await self._handle_events()

tests/protocol/test_ws_stream.py

+29
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,35 @@ async def test_handle_request(stream: WSStream) -> None:
203203
}
204204

205205

206+
@pytest.mark.asyncio
207+
async def test_handle_data_before_acceptance(stream: WSStream) -> None:
208+
await stream.handle(
209+
Request(
210+
stream_id=1,
211+
http_version="2",
212+
headers=[(b"sec-websocket-version", b"13")],
213+
raw_path=b"/?a=b",
214+
method="GET",
215+
)
216+
)
217+
await stream.handle(
218+
Data(
219+
stream_id=1,
220+
data=b"X",
221+
)
222+
)
223+
assert stream.send.call_args_list == [ # type: ignore
224+
call(
225+
Response(
226+
stream_id=1,
227+
headers=[(b"content-length", b"0"), (b"connection", b"close")],
228+
status_code=400,
229+
)
230+
),
231+
call(EndBody(stream_id=1)),
232+
]
233+
234+
206235
@pytest.mark.asyncio
207236
async def test_handle_connection(stream: WSStream) -> None:
208237
await stream.handle(

0 commit comments

Comments
 (0)