Skip to content

Commit d41dabf

Browse files
author
weeping
committed
fix: prevent duplicate Sec-WebSocket-Protocol header
websocket-client library merges header dict entries with the subprotocols parameter into a single comma-joined value (e.g. 'awap,awap,awap'), causing ACS3 signature verification to fail on the server side.
1 parent 098140a commit d41dabf

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

darabonba/websocket.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,12 @@ def connect(self, request: DaraRequest, runtime_object: Any) -> DaraResponse:
208208
if value and key.lower() not in ('host', 'content-length'):
209209
headers[key] = value
210210

211-
if self.websocket_sub_protocol:
211+
if self.websocket_sub_protocol and not any(
212+
k.lower() == 'sec-websocket-protocol' for k in headers
213+
):
212214
headers['Sec-WebSocket-Protocol'] = self.websocket_sub_protocol
213215

214-
subprotocols = [self.websocket_sub_protocol] if self.websocket_sub_protocol else None
216+
subprotocols = None
215217
sslopt = self._configure_tls(runtime_object, parsed.scheme)
216218
proxy_kwargs = self._configure_proxy(parsed, runtime_object, request)
217219

tests/test_websocket.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,51 @@ def test_configure_http_proxy(self):
347347
)
348348
self.assertIn('Proxy-Authorization', auth_request.headers)
349349

350+
def test_no_duplicate_sec_websocket_protocol_header(self):
351+
captured = {}
352+
original_init = __import__('websocket').WebSocketApp.__init__
353+
354+
def patched_init(self_app, url, header=None, on_open=None,
355+
on_message=None, on_error=None, on_close=None,
356+
subprotocols=None, **kwargs):
357+
captured['header'] = header
358+
captured['subprotocols'] = subprotocols
359+
raise ConnectionRefusedError('captured')
360+
361+
import websocket as ws_lib
362+
ws_lib.WebSocketApp.__init__ = patched_init
363+
try:
364+
request = DaraRequest()
365+
request.protocol = 'ws'
366+
request.domain = '127.0.0.1:19999'
367+
request.pathname = '/'
368+
request.headers = {
369+
'host': '127.0.0.1:19999',
370+
'sec-websocket-protocol': 'awap',
371+
}
372+
373+
runtime = RuntimeOptions(
374+
connect_timeout=5000,
375+
read_timeout=5000,
376+
web_socket_ping_interval=0,
377+
web_socket_enable_reconnect=False,
378+
websocket_sub_protocol='awap',
379+
)
380+
handler = MockWebSocketHandler()
381+
client = new_default_websocket_client(handler)
382+
try:
383+
client.connect(request, runtime)
384+
except (ConnectionRefusedError, Exception):
385+
pass
386+
387+
header = captured.get('header', {})
388+
protocol_keys = [k for k in header if k.lower() == 'sec-websocket-protocol']
389+
self.assertEqual(1, len(protocol_keys),
390+
f'Expected exactly 1 protocol header, got: {protocol_keys}')
391+
self.assertIsNone(captured.get('subprotocols'))
392+
finally:
393+
ws_lib.WebSocketApp.__init__ = original_init
394+
350395

351396
if __name__ == '__main__':
352397
unittest.main()

0 commit comments

Comments
 (0)