Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 42 additions & 29 deletions msgpack/_unpacker.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ cdef class Unpacker:
Raises ``OutOfData`` when *packed* is incomplete.
Raises ``FormatError`` when *packed* is not valid msgpack.
Raises ``StackError`` when *packed* contains too nested.
Raises ``RuntimeError`` when ``feed()`` is called while unpacking
is in progress (e.g. from a hook).
Other exceptions can be raised during unpacking.
"""
cdef unpack_context ctx
Expand All @@ -318,6 +320,7 @@ cdef class Unpacker:
cdef object unicode_errors
cdef Py_ssize_t max_buffer_size
cdef uint64_t stream_offset
cdef bint _unpacking

def __dealloc__(self):
unpack_clear(&self.ctx)
Expand Down Expand Up @@ -381,6 +384,7 @@ cdef class Unpacker:
self.buf_head = 0
self.buf_tail = 0
self.stream_offset = 0
self._unpacking = False

if unicode_errors is not None:
self.unicode_errors = unicode_errors
Expand All @@ -398,6 +402,11 @@ cdef class Unpacker:
cdef char* buf
cdef Py_ssize_t buf_len

if self._unpacking:
raise RuntimeError(
"Unpacker.feed() cannot be called while unpacking is in progress"
)

if self.file_like is not None:
raise AssertionError(
"unpacker.feed() is not be able to use with `file_like`.")
Expand Down Expand Up @@ -465,36 +474,40 @@ cdef class Unpacker:
cdef object obj
cdef Py_ssize_t prev_head

while 1:
prev_head = self.buf_head
if prev_head < self.buf_tail:
ret = execute(&self.ctx, self.buf, self.buf_tail, &self.buf_head)
self.stream_offset += self.buf_head - prev_head
else:
ret = 0

if ret == 1:
obj = unpack_data(&self.ctx)
unpack_init(&self.ctx)
return obj
if ret == 0:
if self.file_like is not None:
self.read_from_file()
continue
if iter:
raise StopIteration("No more data to unpack.")
self._unpacking = True
try:
while 1:
prev_head = self.buf_head
if prev_head < self.buf_tail:
ret = execute(&self.ctx, self.buf, self.buf_tail, &self.buf_head)
self.stream_offset += self.buf_head - prev_head
else:
raise OutOfData("No more data to unpack.")

unpack_clear(&self.ctx)
if ret == -2:
raise FormatError
elif ret == -3:
raise StackError
elif PyErr_Occurred():
raise
else:
raise ValueError("Unpack failed: error = %d" % (ret,))
ret = 0

if ret == 1:
obj = unpack_data(&self.ctx)
unpack_init(&self.ctx)
return obj
if ret == 0:
if self.file_like is not None:
self.read_from_file()
continue
if iter:
raise StopIteration("No more data to unpack.")
else:
raise OutOfData("No more data to unpack.")

unpack_clear(&self.ctx)
if ret == -2:
raise FormatError
elif ret == -3:
raise StackError
elif PyErr_Occurred():
raise
else:
raise ValueError("Unpack failed: error = %d" % (ret,))
finally:
self._unpacking = False

@cython.critical_section
def read_bytes(self, Py_ssize_t nbytes):
Expand Down
19 changes: 19 additions & 0 deletions test/test_unpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,22 @@ def hook(code, data):

unpacker.feed(packb({"a": 1}))
assert unpacker.unpack() == {"a": 1}


@mark.skipif(
Unpacker.__module__ == "msgpack.fallback",
reason="reentrant guard is implemented in C extension only",
)
def test_unpacker_reentrant_feed():
import struct

def ext_hook(code, data):
# re-entrant feed on the SAME unpacker, large enough to force a buffer realloc
up.feed(b"\xc0" * 100)
return 0

up = Unpacker(ext_hook=ext_hook, max_buffer_size=64 * 1024 * 1024)
# array(11): [ ExtType(code=5, data=b'A') (fires the re-entrant hook), then 10 more elements ]
up.feed(b"\xdc" + struct.pack(">H", 11) + b"\xd4\x05A" + b"\x2a" * 10)
with raises(RuntimeError):
up.unpack()
Loading