diff --git a/msgpack/fallback.py b/msgpack/fallback.py index cee48e9a..2b2aaa17 100644 --- a/msgpack/fallback.py +++ b/msgpack/fallback.py @@ -501,10 +501,13 @@ def _unpack(self, execute=EX_CONSTRUCT): if execute == EX_READ_ARRAY_HEADER: if typ != TYPE_ARRAY: + # Leave the object unread, as the C extension does. + self._buff_i = self._buf_checkpoint raise ValueError("Expected array") return n if execute == EX_READ_MAP_HEADER: if typ != TYPE_MAP: + self._buff_i = self._buf_checkpoint raise ValueError("Expected map") return n # TODO should we eliminate the recursion? diff --git a/test/test_read_size.py b/test/test_read_size.py index 0f6c1b50..d696ad6a 100644 --- a/test/test_read_size.py +++ b/test/test_read_size.py @@ -1,5 +1,9 @@ """Test Unpacker's read_array_header and read_map_header methods""" +from io import BytesIO + +from pytest import raises + from msgpack import OutOfData, Unpacker, packb UnexpectedTypeException = ValueError @@ -70,3 +74,31 @@ def test_incorrect_type_nested_map(): assert 0, "should raise exception" except UnexpectedTypeException: assert 1, "okay" + + +def test_incorrect_type_does_not_consume_object(): + packed = packb(1) + packb("abc") + packb({"a": 1}) + packb([2]) + unpackers = [Unpacker(), Unpacker(BytesIO(packed))] + unpackers[0].feed(packed) + for unpacker in unpackers: + with raises(UnexpectedTypeException): + unpacker.read_array_header() + assert unpacker.tell() == 0 + assert unpacker.unpack() == 1 + + with raises(UnexpectedTypeException): + unpacker.read_map_header() + assert unpacker.unpack() == "abc" + + # A map is not an array: the stream must stay in sync. + with raises(UnexpectedTypeException): + unpacker.read_array_header() + assert unpacker.read_map_header() == 1 + assert unpacker.unpack() == "a" + assert unpacker.unpack() == 1 + + with raises(UnexpectedTypeException): + unpacker.read_map_header() + assert unpacker.read_array_header() == 1 + assert unpacker.unpack() == 2 + assert unpacker.tell() == len(packed)