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
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ keywords = ["Swagger", "OpenAPI", "REST"]
license = "MIT"
desc = "OpenAPI server and client helper for Julia"
authors = ["JuliaHub Inc."]
version = "0.2.3"
version = "0.2.4"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Expand Down
16 changes: 15 additions & 1 deletion src/client/chunk_readers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function _read_json_chunk(io::IO)
depth = 0
in_string = false
escaped = false
complete = false

while !eof(io)
byte = read(io, UInt8)
Expand All @@ -52,12 +53,22 @@ function _read_json_chunk(io::IO)
depth += 1
elseif byte == close_byte
depth -= 1
depth == 0 && break
if depth == 0
complete = true
break
end
end
end
end
# The stream ended before the structure was balanced: the bytes read are a
# truncated document (a mid-stream connection close / cancelled response).
# Discard them instead of handing a partial document to the JSON parser,
# which would throw "Unexpected end of input". Returning empty makes
# `iterate` treat this as a clean end of stream.
complete || return UInt8[]
elseif first_byte == UInt8('"')
escaped = false
complete = false
read(io, UInt8) # consume opening quote
write(out, UInt8('"'))
while !eof(io)
Expand All @@ -68,9 +79,12 @@ function _read_json_chunk(io::IO)
elseif byte == UInt8('\\')
escaped = true
elseif byte == UInt8('"')
complete = true
break
end
end
# Truncated string (stream closed before the closing quote): discard.
complete || return UInt8[]
else
# number / true / false / null: read until delimiter
while !eof(io)
Expand Down
48 changes: 46 additions & 2 deletions test/chunkreader_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ function jsonchunk3()
end

function jsonchunk4()
# A truncated trailing document (the stream closed mid-object, e.g. a dropped
# or cancelled streaming response) is discarded rather than handed to the JSON
# parser, so iteration ends cleanly instead of throwing "Unexpected end of
# input". The complete documents read before the truncation are still yielded.
buff = Base.BufferStream()
reader = JSONChunkReader(buff)
results = String[]
Expand All @@ -126,10 +130,11 @@ function jsonchunk4()
end

write(buff, "\n\n{\"hello\": \"world\"}\n\n")
write(buff, "{\"hello\": \"world\"\n")
write(buff, "{\"hello\": \"world\"\n") # truncated: no closing brace before EOF
close(buff)
@test_throws TaskFailedException wait(readertask)
wait(readertask) # no throw
@test length(results) == 1
@test JSON.parse(results[1])["hello"] == "world"
end

function rfc7464chunk1()
Expand Down Expand Up @@ -278,6 +283,40 @@ function read_json_chunk_brackets_in_string()
@test eof(io)
end

function read_json_chunk_truncated_object()
# stream closed before the closing brace (e.g. dropped connection mid-object):
# the partial document is discarded rather than parsed.
io = IOBuffer("{\"statuses\":")
@test isempty(_read_json_chunk(io))
@test eof(io)
end

function read_json_chunk_truncated_nested_object()
io = IOBuffer("{\"a\": {\"b\": 1")
@test isempty(_read_json_chunk(io))
@test eof(io)
end

function read_json_chunk_truncated_array()
io = IOBuffer("[1, 2")
@test isempty(_read_json_chunk(io))
@test eof(io)
end

function read_json_chunk_truncated_string()
io = IOBuffer("\"dev/termina")
@test isempty(_read_json_chunk(io))
@test eof(io)
end

function read_json_chunk_complete_then_truncated()
# a complete document is returned; the following truncated one is discarded.
io = IOBuffer("{\"a\":1}{\"b\":")
@test String(_read_json_chunk(io)) == "{\"a\":1}"
@test isempty(_read_json_chunk(io))
@test eof(io)
end

function runtests()
linechunk1()
linechunk2()
Expand All @@ -303,6 +342,11 @@ function runtests()
read_json_chunk_stops_at_boundary()
read_json_chunk_braces_in_string()
read_json_chunk_brackets_in_string()
read_json_chunk_truncated_object()
read_json_chunk_truncated_nested_object()
read_json_chunk_truncated_array()
read_json_chunk_truncated_string()
read_json_chunk_complete_then_truncated()
end

end # module ChunkReaderTests
Loading