mirror of
https://github.com/msgpack/msgpack-python.git
synced 2025-10-19 12:03:15 +00:00
Add StackError and FormatError (#331)
This commit is contained in:
parent
8b6ce53cce
commit
44254dd35e
7 changed files with 120 additions and 48 deletions
|
@ -5,21 +5,25 @@ Release Date: TBD
|
||||||
|
|
||||||
|
|
||||||
Important changes
|
Important changes
|
||||||
------------------
|
-----------------
|
||||||
|
|
||||||
Extension modules are merged. There is ``msgpack._msgpack`` instead of
|
|
||||||
``msgpack._packer`` and ``msgpack._unpacker``. (#314)
|
|
||||||
|
|
||||||
unpacker: Default size limits is smaller than before to avoid DoS attack.
|
|
||||||
If you need to handle large data, you need to specify limits manually.
|
|
||||||
|
|
||||||
|
* unpacker: Default size limits is smaller than before to avoid DoS attack.
|
||||||
|
If you need to handle large data, you need to specify limits manually. (#319)
|
||||||
|
|
||||||
|
|
||||||
Other changes
|
Other changes
|
||||||
--------------
|
-------------
|
||||||
|
|
||||||
Add ``Unpacker.getbuffer()`` method.
|
* Extension modules are merged. There is ``msgpack._msgpack`` instead of
|
||||||
|
``msgpack._packer`` and ``msgpack._unpacker``. (#314)
|
||||||
|
|
||||||
|
* Add ``Unpacker.getbuffer()`` method. (#320)
|
||||||
|
|
||||||
|
* unpacker: ``msgpack.StackError`` is raised when input data contains too
|
||||||
|
nested data. (#331)
|
||||||
|
|
||||||
|
* unpacker: ``msgpack.FormatError`` is raised when input data is not valid
|
||||||
|
msgpack format. (#331)
|
||||||
|
|
||||||
|
|
||||||
0.5.6
|
0.5.6
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -7,7 +7,8 @@ cython:
|
||||||
cython --cplus msgpack/_cmsgpack.pyx
|
cython --cplus msgpack/_cmsgpack.pyx
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test: cython
|
||||||
|
pip install -e .
|
||||||
pytest -v test
|
pytest -v test
|
||||||
MSGPACK_PUREPYTHON=1 pytest -v test
|
MSGPACK_PUREPYTHON=1 pytest -v test
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ from msgpack.exceptions import (
|
||||||
BufferFull,
|
BufferFull,
|
||||||
OutOfData,
|
OutOfData,
|
||||||
ExtraData,
|
ExtraData,
|
||||||
|
FormatError,
|
||||||
|
StackError,
|
||||||
)
|
)
|
||||||
from msgpack import ExtType
|
from msgpack import ExtType
|
||||||
|
|
||||||
|
@ -149,7 +151,11 @@ def unpackb(object packed, object object_hook=None, object list_hook=None,
|
||||||
"""
|
"""
|
||||||
Unpack packed_bytes to object. Returns an unpacked object.
|
Unpack packed_bytes to object. Returns an unpacked object.
|
||||||
|
|
||||||
Raises `ValueError` when `packed` contains extra bytes.
|
Raises ``ExtraData`` when *packed* contains extra bytes.
|
||||||
|
Raises ``ValueError`` when *packed* is incomplete.
|
||||||
|
Raises ``FormatError`` when *packed* is not valid msgpack.
|
||||||
|
Raises ``StackError`` when *packed* contains too nested.
|
||||||
|
Other exceptions can be raised during unpacking.
|
||||||
|
|
||||||
See :class:`Unpacker` for options.
|
See :class:`Unpacker` for options.
|
||||||
"""
|
"""
|
||||||
|
@ -187,6 +193,12 @@ def unpackb(object packed, object object_hook=None, object list_hook=None,
|
||||||
raise ExtraData(obj, PyBytes_FromStringAndSize(buf+off, buf_len-off))
|
raise ExtraData(obj, PyBytes_FromStringAndSize(buf+off, buf_len-off))
|
||||||
return obj
|
return obj
|
||||||
unpack_clear(&ctx)
|
unpack_clear(&ctx)
|
||||||
|
if ret == 0:
|
||||||
|
raise ValueError("Unpack failed: incomplete input")
|
||||||
|
elif ret == -2:
|
||||||
|
raise FormatError
|
||||||
|
elif ret == -3:
|
||||||
|
raise StackError
|
||||||
raise ValueError("Unpack failed: error = %d" % (ret,))
|
raise ValueError("Unpack failed: error = %d" % (ret,))
|
||||||
|
|
||||||
|
|
||||||
|
@ -201,7 +213,7 @@ def unpack(object stream, **kwargs):
|
||||||
cdef class Unpacker(object):
|
cdef class Unpacker(object):
|
||||||
"""Streaming unpacker.
|
"""Streaming unpacker.
|
||||||
|
|
||||||
arguments:
|
Arguments:
|
||||||
|
|
||||||
:param file_like:
|
:param file_like:
|
||||||
File-like object having `.read(n)` method.
|
File-like object having `.read(n)` method.
|
||||||
|
@ -279,6 +291,12 @@ cdef class Unpacker(object):
|
||||||
unpacker.feed(buf)
|
unpacker.feed(buf)
|
||||||
for o in unpacker:
|
for o in unpacker:
|
||||||
process(o)
|
process(o)
|
||||||
|
|
||||||
|
Raises ``ExtraData`` when *packed* contains extra bytes.
|
||||||
|
Raises ``OutOfData`` when *packed* is incomplete.
|
||||||
|
Raises ``FormatError`` when *packed* is not valid msgpack.
|
||||||
|
Raises ``StackError`` when *packed* contains too nested.
|
||||||
|
Other exceptions can be raised during unpacking.
|
||||||
"""
|
"""
|
||||||
cdef unpack_context ctx
|
cdef unpack_context ctx
|
||||||
cdef char* buf
|
cdef char* buf
|
||||||
|
@ -451,6 +469,10 @@ cdef class Unpacker(object):
|
||||||
raise StopIteration("No more data to unpack.")
|
raise StopIteration("No more data to unpack.")
|
||||||
else:
|
else:
|
||||||
raise OutOfData("No more data to unpack.")
|
raise OutOfData("No more data to unpack.")
|
||||||
|
elif ret == -2:
|
||||||
|
raise FormatError
|
||||||
|
elif ret == -3:
|
||||||
|
raise StackError
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unpack failed: error = %d" % (ret,))
|
raise ValueError("Unpack failed: error = %d" % (ret,))
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ class UnpackException(Exception):
|
||||||
Exception instead.
|
Exception instead.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BufferFull(UnpackException):
|
class BufferFull(UnpackException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -14,6 +15,14 @@ class OutOfData(UnpackException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FormatError(ValueError, UnpackException):
|
||||||
|
"""Invalid msgpack format"""
|
||||||
|
|
||||||
|
|
||||||
|
class StackError(ValueError, UnpackException):
|
||||||
|
"""Too nested"""
|
||||||
|
|
||||||
|
|
||||||
# Deprecated. Use ValueError instead
|
# Deprecated. Use ValueError instead
|
||||||
UnpackValueError = ValueError
|
UnpackValueError = ValueError
|
||||||
|
|
||||||
|
@ -24,6 +33,7 @@ class ExtraData(UnpackValueError):
|
||||||
This exception is raised while only one-shot (not streaming)
|
This exception is raised while only one-shot (not streaming)
|
||||||
unpack.
|
unpack.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, unpacked, extra):
|
def __init__(self, unpacked, extra):
|
||||||
self.unpacked = unpacked
|
self.unpacked = unpacked
|
||||||
self.extra = extra
|
self.extra = extra
|
||||||
|
@ -32,7 +42,7 @@ class ExtraData(UnpackValueError):
|
||||||
return "unpack(b) received extra data."
|
return "unpack(b) received extra data."
|
||||||
|
|
||||||
|
|
||||||
#Deprecated. Use Exception instead to catch all exception during packing.
|
# Deprecated. Use Exception instead to catch all exception during packing.
|
||||||
PackException = Exception
|
PackException = Exception
|
||||||
PackValueError = ValueError
|
PackValueError = ValueError
|
||||||
PackOverflowError = OverflowError
|
PackOverflowError = OverflowError
|
||||||
|
|
|
@ -18,6 +18,16 @@ else:
|
||||||
def dict_iteritems(d):
|
def dict_iteritems(d):
|
||||||
return d.iteritems()
|
return d.iteritems()
|
||||||
|
|
||||||
|
if sys.version_info < (3, 5):
|
||||||
|
# Ugly hack...
|
||||||
|
RecursionError = RuntimeError
|
||||||
|
|
||||||
|
def _is_recursionerror(e):
|
||||||
|
return len(e.args) == 1 and isinstance(e.args[0], str) and \
|
||||||
|
e.args[0].startswith('maximum recursion depth exceeded')
|
||||||
|
else:
|
||||||
|
def _is_recursionerror(e):
|
||||||
|
return True
|
||||||
|
|
||||||
if hasattr(sys, 'pypy_version_info'):
|
if hasattr(sys, 'pypy_version_info'):
|
||||||
# cStringIO is slow on PyPy, StringIO is faster. However: PyPy's own
|
# cStringIO is slow on PyPy, StringIO is faster. However: PyPy's own
|
||||||
|
@ -52,7 +62,10 @@ else:
|
||||||
from msgpack.exceptions import (
|
from msgpack.exceptions import (
|
||||||
BufferFull,
|
BufferFull,
|
||||||
OutOfData,
|
OutOfData,
|
||||||
ExtraData)
|
ExtraData,
|
||||||
|
FormatError,
|
||||||
|
StackError,
|
||||||
|
)
|
||||||
|
|
||||||
from msgpack import ExtType
|
from msgpack import ExtType
|
||||||
|
|
||||||
|
@ -109,7 +122,12 @@ def unpackb(packed, **kwargs):
|
||||||
"""
|
"""
|
||||||
Unpack an object from `packed`.
|
Unpack an object from `packed`.
|
||||||
|
|
||||||
Raises `ExtraData` when `packed` contains extra bytes.
|
Raises ``ExtraData`` when *packed* contains extra bytes.
|
||||||
|
Raises ``ValueError`` when *packed* is incomplete.
|
||||||
|
Raises ``FormatError`` when *packed* is not valid msgpack.
|
||||||
|
Raises ``StackError`` when *packed* contains too nested.
|
||||||
|
Other exceptions can be raised during unpacking.
|
||||||
|
|
||||||
See :class:`Unpacker` for options.
|
See :class:`Unpacker` for options.
|
||||||
"""
|
"""
|
||||||
unpacker = Unpacker(None, **kwargs)
|
unpacker = Unpacker(None, **kwargs)
|
||||||
|
@ -117,7 +135,11 @@ def unpackb(packed, **kwargs):
|
||||||
try:
|
try:
|
||||||
ret = unpacker._unpack()
|
ret = unpacker._unpack()
|
||||||
except OutOfData:
|
except OutOfData:
|
||||||
raise ValueError("Data is not enough.")
|
raise ValueError("Unpack failed: incomplete input")
|
||||||
|
except RecursionError as e:
|
||||||
|
if _is_recursionerror(e):
|
||||||
|
raise StackError
|
||||||
|
raise
|
||||||
if unpacker._got_extradata():
|
if unpacker._got_extradata():
|
||||||
raise ExtraData(ret, unpacker._get_extradata())
|
raise ExtraData(ret, unpacker._get_extradata())
|
||||||
return ret
|
return ret
|
||||||
|
@ -211,6 +233,12 @@ class Unpacker(object):
|
||||||
unpacker.feed(buf)
|
unpacker.feed(buf)
|
||||||
for o in unpacker:
|
for o in unpacker:
|
||||||
process(o)
|
process(o)
|
||||||
|
|
||||||
|
Raises ``ExtraData`` when *packed* contains extra bytes.
|
||||||
|
Raises ``OutOfData`` when *packed* is incomplete.
|
||||||
|
Raises ``FormatError`` when *packed* is not valid msgpack.
|
||||||
|
Raises ``StackError`` when *packed* contains too nested.
|
||||||
|
Other exceptions can be raised during unpacking.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, file_like=None, read_size=0, use_list=True, raw=True,
|
def __init__(self, file_like=None, read_size=0, use_list=True, raw=True,
|
||||||
|
@ -561,7 +589,7 @@ class Unpacker(object):
|
||||||
raise ValueError("%s exceeds max_map_len(%s)", n, self._max_map_len)
|
raise ValueError("%s exceeds max_map_len(%s)", n, self._max_map_len)
|
||||||
typ = TYPE_MAP
|
typ = TYPE_MAP
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown header: 0x%x" % b)
|
raise FormatError("Unknown header: 0x%x" % b)
|
||||||
return typ, n, obj
|
return typ, n, obj
|
||||||
|
|
||||||
def _unpack(self, execute=EX_CONSTRUCT):
|
def _unpack(self, execute=EX_CONSTRUCT):
|
||||||
|
@ -637,6 +665,8 @@ class Unpacker(object):
|
||||||
except OutOfData:
|
except OutOfData:
|
||||||
self._consume()
|
self._consume()
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
except RecursionError:
|
||||||
|
raise StackError
|
||||||
|
|
||||||
next = __next__
|
next = __next__
|
||||||
|
|
||||||
|
@ -645,7 +675,10 @@ class Unpacker(object):
|
||||||
self._consume()
|
self._consume()
|
||||||
|
|
||||||
def unpack(self):
|
def unpack(self):
|
||||||
ret = self._unpack(EX_CONSTRUCT)
|
try:
|
||||||
|
ret = self._unpack(EX_CONSTRUCT)
|
||||||
|
except RecursionError:
|
||||||
|
raise StackError
|
||||||
self._consume()
|
self._consume()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ static inline int unpack_execute(unpack_context* ctx, const char* data, Py_ssize
|
||||||
goto _fixed_trail_again
|
goto _fixed_trail_again
|
||||||
|
|
||||||
#define start_container(func, count_, ct_) \
|
#define start_container(func, count_, ct_) \
|
||||||
if(top >= MSGPACK_EMBED_STACK_SIZE) { goto _failed; } /* FIXME */ \
|
if(top >= MSGPACK_EMBED_STACK_SIZE) { ret = -3; goto _end; } \
|
||||||
if(construct_cb(func)(user, count_, &stack[top].obj) < 0) { goto _failed; } \
|
if(construct_cb(func)(user, count_, &stack[top].obj) < 0) { goto _failed; } \
|
||||||
if((count_) == 0) { obj = stack[top].obj; \
|
if((count_) == 0) { obj = stack[top].obj; \
|
||||||
if (construct_cb(func##_end)(user, &obj) < 0) { goto _failed; } \
|
if (construct_cb(func##_end)(user, &obj) < 0) { goto _failed; } \
|
||||||
|
@ -132,27 +132,6 @@ static inline int unpack_execute(unpack_context* ctx, const char* data, Py_ssize
|
||||||
stack[top].size = count_; \
|
stack[top].size = count_; \
|
||||||
stack[top].count = 0; \
|
stack[top].count = 0; \
|
||||||
++top; \
|
++top; \
|
||||||
/*printf("container %d count %d stack %d\n",stack[top].obj,count_,top);*/ \
|
|
||||||
/*printf("stack push %d\n", top);*/ \
|
|
||||||
/* FIXME \
|
|
||||||
if(top >= stack_size) { \
|
|
||||||
if(stack_size == MSGPACK_EMBED_STACK_SIZE) { \
|
|
||||||
size_t csize = sizeof(unpack_stack) * MSGPACK_EMBED_STACK_SIZE; \
|
|
||||||
size_t nsize = csize * 2; \
|
|
||||||
unpack_stack* tmp = (unpack_stack*)malloc(nsize); \
|
|
||||||
if(tmp == NULL) { goto _failed; } \
|
|
||||||
memcpy(tmp, ctx->stack, csize); \
|
|
||||||
ctx->stack = stack = tmp; \
|
|
||||||
ctx->stack_size = stack_size = MSGPACK_EMBED_STACK_SIZE * 2; \
|
|
||||||
} else { \
|
|
||||||
size_t nsize = sizeof(unpack_stack) * ctx->stack_size * 2; \
|
|
||||||
unpack_stack* tmp = (unpack_stack*)realloc(ctx->stack, nsize); \
|
|
||||||
if(tmp == NULL) { goto _failed; } \
|
|
||||||
ctx->stack = stack = tmp; \
|
|
||||||
ctx->stack_size = stack_size = stack_size * 2; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
*/ \
|
|
||||||
goto _header_again
|
goto _header_again
|
||||||
|
|
||||||
#define NEXT_CS(p) ((unsigned int)*p & 0x1f)
|
#define NEXT_CS(p) ((unsigned int)*p & 0x1f)
|
||||||
|
@ -229,7 +208,8 @@ static inline int unpack_execute(unpack_context* ctx, const char* data, Py_ssize
|
||||||
case 0xdf: // map 32
|
case 0xdf: // map 32
|
||||||
again_fixed_trail(NEXT_CS(p), 2 << (((unsigned int)*p) & 0x01));
|
again_fixed_trail(NEXT_CS(p), 2 << (((unsigned int)*p) & 0x01));
|
||||||
default:
|
default:
|
||||||
goto _failed;
|
ret = -2;
|
||||||
|
goto _end;
|
||||||
}
|
}
|
||||||
SWITCH_RANGE(0xa0, 0xbf) // FixRaw
|
SWITCH_RANGE(0xa0, 0xbf) // FixRaw
|
||||||
again_fixed_trail_if_zero(ACS_RAW_VALUE, ((unsigned int)*p & 0x1f), _raw_zero);
|
again_fixed_trail_if_zero(ACS_RAW_VALUE, ((unsigned int)*p & 0x1f), _raw_zero);
|
||||||
|
@ -239,7 +219,8 @@ static inline int unpack_execute(unpack_context* ctx, const char* data, Py_ssize
|
||||||
start_container(_map, ((unsigned int)*p) & 0x0f, CT_MAP_KEY);
|
start_container(_map, ((unsigned int)*p) & 0x0f, CT_MAP_KEY);
|
||||||
|
|
||||||
SWITCH_RANGE_DEFAULT
|
SWITCH_RANGE_DEFAULT
|
||||||
goto _failed;
|
ret = -2;
|
||||||
|
goto _end;
|
||||||
SWITCH_RANGE_END
|
SWITCH_RANGE_END
|
||||||
// end CS_HEADER
|
// end CS_HEADER
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
from msgpack import packb, unpackb
|
from msgpack import packb, unpackb, Unpacker, FormatError, StackError, OutOfData
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
@ -19,13 +19,34 @@ def test_raise_on_find_unsupported_value():
|
||||||
def test_raise_from_object_hook():
|
def test_raise_from_object_hook():
|
||||||
def hook(obj):
|
def hook(obj):
|
||||||
raise DummyException
|
raise DummyException
|
||||||
|
|
||||||
raises(DummyException, unpackb, packb({}), object_hook=hook)
|
raises(DummyException, unpackb, packb({}), object_hook=hook)
|
||||||
raises(DummyException, unpackb, packb({'fizz': 'buzz'}), object_hook=hook)
|
raises(DummyException, unpackb, packb({"fizz": "buzz"}), object_hook=hook)
|
||||||
raises(DummyException, unpackb, packb({'fizz': 'buzz'}), object_pairs_hook=hook)
|
raises(DummyException, unpackb, packb({"fizz": "buzz"}), object_pairs_hook=hook)
|
||||||
raises(DummyException, unpackb, packb({'fizz': {'buzz': 'spam'}}), object_hook=hook)
|
raises(DummyException, unpackb, packb({"fizz": {"buzz": "spam"}}), object_hook=hook)
|
||||||
raises(DummyException, unpackb, packb({'fizz': {'buzz': 'spam'}}), object_pairs_hook=hook)
|
raises(
|
||||||
|
DummyException,
|
||||||
|
unpackb,
|
||||||
|
packb({"fizz": {"buzz": "spam"}}),
|
||||||
|
object_pairs_hook=hook,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_invalidvalue():
|
def test_invalidvalue():
|
||||||
|
incomplete = b"\xd9\x97#DL_" # raw8 - length=0x97
|
||||||
with raises(ValueError):
|
with raises(ValueError):
|
||||||
unpackb(b'\xd9\x97#DL_')
|
unpackb(incomplete)
|
||||||
|
|
||||||
|
with raises(OutOfData):
|
||||||
|
unpacker = Unpacker()
|
||||||
|
unpacker.feed(incomplete)
|
||||||
|
unpacker.unpack()
|
||||||
|
|
||||||
|
with raises(FormatError):
|
||||||
|
unpackb(b"\xc1") # (undefined tag)
|
||||||
|
|
||||||
|
with raises(FormatError):
|
||||||
|
unpackb(b"\x91\xc1") # fixarray(len=1) [ (undefined tag) ]
|
||||||
|
|
||||||
|
with raises(StackError):
|
||||||
|
unpackb(b"\x91" * 3000) # nested fixarray(len=1)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue