Skip to content

Commit 024609e

Browse files
Fix race condition in handling of binary attachments
Fixes #37
1 parent 047712d commit 024609e

File tree

4 files changed

+40
-34
lines changed

4 files changed

+40
-34
lines changed

socketio/packet.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def __init__(self, packet_type=EVENT, data=None, namespace=None, id=None,
2828
else:
2929
raise ValueError('Packet does not support binary payload.')
3030
self.attachment_count = 0
31+
self.attachments = []
3132
if encoded_packet:
3233
self.attachment_count = self.decode(encoded_packet)
3334

@@ -100,11 +101,21 @@ def decode(self, encoded_packet):
100101
self.data = self.json.loads(ep)
101102
return attachment_count
102103

104+
def add_attachment(self, attachment):
105+
if self.attachment_count <= len(self.attachments):
106+
raise ValueError('Unexpected binary attachment')
107+
self.attachments.append(attachment)
108+
if self.attachment_count == len(self.attachments):
109+
self.reconstruct_binary(self.attachments)
110+
return True
111+
return False
112+
103113
def reconstruct_binary(self, attachments):
104114
"""Reconstruct a decoded packet using the given list of binary
105115
attachments.
106116
"""
107-
self.data = self._reconstruct_binary_internal(self.data, attachments)
117+
self.data = self._reconstruct_binary_internal(self.data,
118+
self.attachments)
108119

109120
def _reconstruct_binary_internal(self, data, attachments):
110121
if isinstance(data, list):

socketio/server.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,7 @@ def __init__(self, client_manager=None, logger=False, binary=False,
7878
self.environ = {}
7979
self.handlers = {}
8080

81-
self._binary_packet = None
82-
self._attachment_count = 0
83-
self._attachments = []
81+
self._binary_packet = []
8482

8583
if not isinstance(logger, bool):
8684
self.logger = logger
@@ -434,22 +432,14 @@ def _handle_eio_connect(self, sid, environ):
434432

435433
def _handle_eio_message(self, sid, data):
436434
"""Dispatch Engine.IO messages."""
437-
if self._attachment_count > 0:
438-
self._attachments.append(data)
439-
self._attachment_count -= 1
440-
441-
if self._attachment_count == 0:
442-
self._binary_packet.reconstruct_binary(self._attachments)
443-
if self._binary_packet.packet_type == packet.BINARY_EVENT:
444-
self._handle_event(sid, self._binary_packet.namespace,
445-
self._binary_packet.id,
446-
self._binary_packet.data)
435+
if len(self._binary_packet):
436+
pkt = self._binary_packet[0]
437+
if pkt.add_attachment(data):
438+
self._binary_packet.pop(0)
439+
if pkt.packet_type == packet.BINARY_EVENT:
440+
self._handle_event(sid, pkt.namespace, pkt.id, pkt.data)
447441
else:
448-
self._handle_ack(sid, self._binary_packet.namespace,
449-
self._binary_packet.id,
450-
self._binary_packet.data)
451-
self._binary_packet = None
452-
self._attachments = []
442+
self._handle_ack(sid, pkt.namespace, pkt.id, pkt.data)
453443
else:
454444
pkt = packet.Packet(encoded_packet=data)
455445
if pkt.packet_type == packet.CONNECT:
@@ -462,9 +452,7 @@ def _handle_eio_message(self, sid, data):
462452
self._handle_ack(sid, pkt.namespace, pkt.id, pkt.data)
463453
elif pkt.packet_type == packet.BINARY_EVENT or \
464454
pkt.packet_type == packet.BINARY_ACK:
465-
self._binary_packet = pkt
466-
self._attachments = []
467-
self._attachment_count = pkt.attachment_count
455+
self._binary_packet.append(pkt)
468456
elif pkt.packet_type == packet.ERROR:
469457
raise ValueError('Unexpected ERROR packet.')
470458
else:

tests/test_packet.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def test_encode_binary_event_packet(self):
5050

5151
def test_decode_binary_event_packet(self):
5252
pkt = packet.Packet(encoded_packet='51-{"_placeholder":true,"num":0}')
53-
pkt.reconstruct_binary([b'1234'])
53+
self.assertTrue(pkt.add_attachment(b'1234'))
5454
self.assertEqual(pkt.packet_type, packet.BINARY_EVENT)
5555
self.assertEqual(pkt.data, b'1234')
5656

@@ -78,7 +78,7 @@ def test_encode_binary_ack_packet(self):
7878

7979
def test_decode_binary_ack_packet(self):
8080
pkt = packet.Packet(encoded_packet='61-{"_placeholder":true,"num":0}')
81-
pkt.reconstruct_binary([b'1234'])
81+
self.assertTrue(pkt.add_attachment(b'1234'))
8282
self.assertEqual(pkt.packet_type, packet.BINARY_ACK)
8383
self.assertEqual(pkt.data, b'1234')
8484

@@ -157,7 +157,8 @@ def test_decode_many_binary(self):
157157
pkt = packet.Packet(encoded_packet=(
158158
'52-{"a":"123","b":{"_placeholder":true,"num":0},'
159159
'"c":[{"_placeholder":true,"num":1},123]}'))
160-
pkt.reconstruct_binary([b'456', b'789'])
160+
self.assertFalse(pkt.add_attachment(b'456'))
161+
self.assertTrue(pkt.add_attachment(b'789'))
161162
self.assertEqual(pkt.packet_type, packet.BINARY_EVENT)
162163
self.assertEqual(pkt.data['a'], '123')
163164
self.assertEqual(pkt.data['b'], b'456')
@@ -167,12 +168,21 @@ def test_decode_many_binary_ack(self):
167168
pkt = packet.Packet(encoded_packet=(
168169
'62-{"a":"123","b":{"_placeholder":true,"num":0},'
169170
'"c":[{"_placeholder":true,"num":1},123]}'))
170-
pkt.reconstruct_binary([b'456', b'789'])
171+
self.assertFalse(pkt.add_attachment(b'456'))
172+
self.assertTrue(pkt.add_attachment(b'789'))
171173
self.assertEqual(pkt.packet_type, packet.BINARY_ACK)
172174
self.assertEqual(pkt.data['a'], '123')
173175
self.assertEqual(pkt.data['b'], b'456')
174176
self.assertEqual(pkt.data['c'], [b'789', 123])
175177

178+
def test_decode_too_many_binary_packets(self):
179+
pkt = packet.Packet(encoded_packet=(
180+
'62-{"a":"123","b":{"_placeholder":true,"num":0},'
181+
'"c":[{"_placeholder":true,"num":1},123]}'))
182+
self.assertFalse(pkt.add_attachment(b'456'))
183+
self.assertTrue(pkt.add_attachment(b'789'))
184+
self.assertRaises(ValueError, pkt.add_attachment, b'123')
185+
176186
def test_data_is_binary_list(self):
177187
pkt = packet.Packet()
178188
self.assertFalse(pkt._data_is_binary([six.text_type('foo')]))

tests/test_server.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -283,22 +283,19 @@ def test_handle_event_binary(self, eio):
283283
s._handle_eio_message('123', '52-["my message","a",'
284284
'{"_placeholder":true,"num":1},'
285285
'{"_placeholder":true,"num":0}]')
286-
self.assertEqual(s._attachment_count, 2)
287286
s._handle_eio_message('123', b'foo')
288-
self.assertEqual(s._attachment_count, 1)
289287
s._handle_eio_message('123', b'bar')
290-
self.assertEqual(s._attachment_count, 0)
291288
handler.assert_called_once_with('123', 'a', b'bar', b'foo')
292289

293290
def test_handle_event_binary_ack(self, eio):
294-
s = server.Server()
291+
mgr = mock.MagicMock()
292+
s = server.Server(client_manager=mgr)
295293
s.manager.initialize(s)
296-
s._handle_eio_message('123', '61-1["my message","a",'
294+
s._handle_eio_message('123', '61-321["my message","a",'
297295
'{"_placeholder":true,"num":0}]')
298-
self.assertEqual(s._attachment_count, 1)
299-
# the following call should not raise an exception in spite of the
300-
# callback id being invalid
301296
s._handle_eio_message('123', b'foo')
297+
mgr.trigger_callback.assert_called_once_with(
298+
'123', '/', 321, ['my message', 'a', b'foo'])
302299

303300
def test_handle_event_with_ack(self, eio):
304301
s = server.Server()

0 commit comments

Comments
 (0)