Skip to content
Merged
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
19 changes: 16 additions & 3 deletions rapidjson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,10 @@ class PyReadStreamWrapper {
return offset + pos;
}

bool IsEnd() const {
return eof;
}

void Flush() {
assert(false);
}
Expand Down Expand Up @@ -2049,6 +2053,7 @@ do_decode(PyObject* decoder, const char* jsonStr, Py_ssize_t jsonStrLen,
{
PyHandler handler(decoder, objectHook, datetimeMode, uuidMode, numberMode);
Reader reader;
ParseResult result;

if (jsonStr != NULL) {
char* jsonStrCopy = (char*) PyMem_Malloc(sizeof(char) * (jsonStrLen+1));
Expand All @@ -2062,15 +2067,23 @@ do_decode(PyObject* decoder, const char* jsonStr, Py_ssize_t jsonStrLen,

DECODE(reader, kParseInsituFlag, ss, handler);

result.Set(reader.GetParseErrorCode(), reader.GetErrorOffset());
if (!result.IsError() && ss.Tell() < (size_t) jsonStrLen)
result.Set(kParseErrorDocumentRootNotSingular, ss.Tell());

PyMem_Free(jsonStrCopy);
} else {
PyReadStreamWrapper sw(jsonStream, chunkSize);

DECODE(reader, kParseNoFlags, sw, handler);

result.Set(reader.GetParseErrorCode(), reader.GetErrorOffset());
if (!result.IsError() && !sw.IsEnd())
result.Set(kParseErrorDocumentRootNotSingular, sw.Tell());
}

if (reader.HasParseError()) {
size_t offset = reader.GetErrorOffset();
if (result.IsError()) {
size_t offset = result.Offset();

if (PyErr_Occurred()) {
PyObject* etype;
Expand All @@ -2092,7 +2105,7 @@ do_decode(PyObject* decoder, const char* jsonStr, Py_ssize_t jsonStrLen,
}
else
PyErr_Format(decode_error, "Parse error at offset %zu: %s",
offset, GetParseError_En(reader.GetParseErrorCode()));
offset, GetParseError_En(result.Code()));

Py_XDECREF(handler.root);
return NULL;
Expand Down
13 changes: 13 additions & 0 deletions tests/test_streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ def test_invalid_chunk_size(cs):
rj.load(s, chunk_size=cs)


@pytest.mark.parametrize('chunk_size', [4, 5, 65536])
@pytest.mark.parametrize('stream_type', [io.StringIO, io.BytesIO])
@pytest.mark.parametrize('use_decoder', [False, True])
def test_null_at_chunk_boundary(chunk_size, stream_type, use_decoder):
document = '1234\0garbage'
stream = stream_type(document.encode() if stream_type is io.BytesIO else document)
with pytest.raises(rj.JSONDecodeError, match='Parse error at offset 4:'):
if use_decoder:
rj.Decoder()(stream, chunk_size=chunk_size)
else:
rj.load(stream, chunk_size=chunk_size)


class ChunkedStream(io.StringIO):
def __init__(self):
super().__init__()
Expand Down
21 changes: 21 additions & 0 deletions tests/test_unicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,24 @@ def test_non_utf8_bytes(dumps):
value = b'\xff\xf0'
with pytest.raises(UnicodeDecodeError, match="'utf-8' codec can't decode byte"):
dumps(value)


@pytest.mark.parametrize('prefix', ['123', 'true', 'null', '{}', '[] \n', '"\u03b1"'])
@pytest.mark.parametrize('suffix', ['', 'garbage'])
def test_null_after_document(prefix, suffix, loads):
offset = len(prefix.encode('utf-8'))
with pytest.raises(rapidjson.JSONDecodeError,
match=f'Parse error at offset {offset}: The document root'):
loads(prefix + '\0' + suffix)


@pytest.mark.parametrize('value', ['\0', 'before\0after'])
def test_escaped_null_roundtrip(value, dumps, loads):
assert loads(dumps(value)) == value


@pytest.mark.parametrize('input_type', [bytes, bytearray])
@pytest.mark.parametrize('decoder', [rapidjson.loads, rapidjson.Decoder()])
def test_null_after_document_bytes(input_type, decoder):
with pytest.raises(rapidjson.JSONDecodeError, match='Parse error at offset 3:'):
decoder(input_type(b'123\0garbage'))