Skip to content

Commit 5c61dc6

Browse files
authored
Add ZlibError type and move initialize into startproc (#93)
* Add `ZlibError` type and move `initialize` into `startproc` * fix comment typo
1 parent 98ccccd commit 5c61dc6

File tree

4 files changed

+132
-42
lines changed

4 files changed

+132
-42
lines changed

Diff for: src/compression.jl

+30-16
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,6 @@ end
155155
# Methods
156156
# -------
157157

158-
function TranscodingStreams.initialize(codec::CompressorCodec)
159-
code = deflate_init!(codec.zstream, codec.level, codec.windowbits)
160-
if code != Z_OK
161-
zerror(codec.zstream, code)
162-
end
163-
return
164-
end
165-
166158
function TranscodingStreams.finalize(codec::CompressorCodec)
167159
zstream = codec.zstream
168160
if zstream.state != C_NULL
@@ -174,33 +166,55 @@ function TranscodingStreams.finalize(codec::CompressorCodec)
174166
return
175167
end
176168

177-
function TranscodingStreams.startproc(codec::CompressorCodec, state::Symbol, error::Error)
178-
code = deflate_reset!(codec.zstream)
179-
if code == Z_OK
180-
return :ok
169+
function TranscodingStreams.startproc(codec::CompressorCodec, state::Symbol, error_ref::Error)
170+
if codec.zstream.state == C_NULL
171+
code = deflate_init!(codec.zstream, codec.level, codec.windowbits)
172+
# errors in deflate_init! do not require clean up, so just throw
173+
if code == Z_OK
174+
return :ok
175+
elseif code == Z_MEM_ERROR
176+
throw(OutOfMemoryError())
177+
elseif code == Z_STREAM_ERROR
178+
error("Z_STREAM_ERROR: invalid parameter, this should be caught in the codec constructor")
179+
elseif code == Z_VERSION_ERROR
180+
error("Z_VERSION_ERROR: zlib library version is incompatible")
181+
else
182+
error("unexpected libz error code: $(code)")
183+
end
181184
else
182-
error[] = ErrorException(zlib_error_message(codec.zstream, code))
183-
return :error
185+
code = deflate_reset!(codec.zstream)
186+
# errors in deflate_reset! do not require clean up, so just throw
187+
if code == Z_OK
188+
return :ok
189+
elseif code == Z_STREAM_ERROR
190+
error("Z_STREAM_ERROR: the source stream state was inconsistent")
191+
else
192+
error("unexpected libz error code: $(code)")
193+
end
184194
end
185195
end
186196

187-
function TranscodingStreams.process(codec::CompressorCodec, input::Memory, output::Memory, error::Error)
197+
function TranscodingStreams.process(codec::CompressorCodec, input::Memory, output::Memory, error_ref::Error)
188198
zstream = codec.zstream
199+
if zstream.state == C_NULL
200+
error("startproc must be called before process")
201+
end
189202
zstream.next_in = input.ptr
190203
avail_in = min(input.size, typemax(UInt32))
191204
zstream.avail_in = avail_in
192205
zstream.next_out = output.ptr
193206
avail_out = min(output.size, typemax(UInt32))
194207
zstream.avail_out = avail_out
195208
code = deflate!(zstream, zstream.avail_in > 0 ? Z_NO_FLUSH : Z_FINISH)
209+
@assert code != Z_STREAM_ERROR # state not clobbered
196210
Δin = Int(avail_in - zstream.avail_in)
197211
Δout = Int(avail_out - zstream.avail_out)
198212
if code == Z_OK
199213
return Δin, Δout, :ok
200214
elseif code == Z_STREAM_END
201215
return Δin, Δout, :end
202216
else
203-
error[] = ErrorException(zlib_error_message(zstream, code))
217+
error_ref[] = ErrorException(zlib_error_message(zstream, code))
204218
return Δin, Δout, :error
205219
end
206220
end

Diff for: src/decompression.jl

+38-16
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,6 @@ end
143143
# Methods
144144
# -------
145145

146-
function TranscodingStreams.initialize(codec::DecompressorCodec)
147-
code = inflate_init!(codec.zstream, codec.windowbits)
148-
if code != Z_OK
149-
zerror(codec.zstream, code)
150-
end
151-
return
152-
end
153-
154146
function TranscodingStreams.finalize(codec::DecompressorCodec)
155147
zstream = codec.zstream
156148
if zstream.state != C_NULL
@@ -162,18 +154,42 @@ function TranscodingStreams.finalize(codec::DecompressorCodec)
162154
return
163155
end
164156

165-
function TranscodingStreams.startproc(codec::DecompressorCodec, ::Symbol, error::Error)
166-
code = inflate_reset!(codec.zstream)
167-
if code == Z_OK
168-
return :ok
157+
function TranscodingStreams.startproc(codec::DecompressorCodec, ::Symbol, error_ref::Error)
158+
# indicate that no input data is being provided for future zlib compat
159+
codec.zstream.next_in = C_NULL
160+
codec.zstream.avail_in = 0
161+
if codec.zstream.state == C_NULL
162+
code = inflate_init!(codec.zstream, codec.windowbits)
163+
# errors in inflate_init! do not require clean up, so just throw
164+
if code == Z_OK
165+
return :ok
166+
elseif code == Z_MEM_ERROR
167+
throw(OutOfMemoryError())
168+
elseif code == Z_STREAM_ERROR
169+
error("Z_STREAM_ERROR: invalid parameter, this should be caught in the codec constructor")
170+
elseif code == Z_VERSION_ERROR
171+
error("Z_VERSION_ERROR: zlib library version is incompatible")
172+
else
173+
error("unexpected libz error code: $(code)")
174+
end
169175
else
170-
error[] = ErrorException(zlib_error_message(codec.zstream, code))
171-
return :error
176+
code = inflate_reset!(codec.zstream)
177+
# errors in inflate_reset! do not require clean up, so just throw
178+
if code == Z_OK
179+
return :ok
180+
elseif code == Z_STREAM_ERROR
181+
error("Z_STREAM_ERROR: the source stream state was inconsistent")
182+
else
183+
error("unexpected libz error code: $(code)")
184+
end
172185
end
173186
end
174187

175-
function TranscodingStreams.process(codec::DecompressorCodec, input::Memory, output::Memory, error::Error)
188+
function TranscodingStreams.process(codec::DecompressorCodec, input::Memory, output::Memory, error_ref::Error)
176189
zstream = codec.zstream
190+
if zstream.state == C_NULL
191+
error("startproc must be called before process")
192+
end
177193
zstream.next_in = input.ptr
178194

179195
avail_in = min(input.size, typemax(UInt32))
@@ -182,14 +198,20 @@ function TranscodingStreams.process(codec::DecompressorCodec, input::Memory, out
182198
avail_out = min(output.size, typemax(UInt32))
183199
zstream.avail_out = avail_out
184200
code = inflate!(zstream, Z_NO_FLUSH)
201+
@assert code != Z_STREAM_ERROR # state not clobbered
185202
Δin = Int(avail_in - zstream.avail_in)
186203
Δout = Int(avail_out - zstream.avail_out)
187204
if code == Z_OK
188205
return Δin, Δout, :ok
189206
elseif code == Z_STREAM_END
190207
return Δin, Δout, :end
208+
elseif code == Z_MEM_ERROR
209+
throw(OutOfMemoryError())
210+
elseif code == Z_BUF_ERROR && iszero(input.size)
211+
error_ref[] = ZlibError("the compressed stream may be truncated")
212+
return Δin, Δout, :error
191213
else
192-
error[] = ErrorException(zlib_error_message(zstream, code))
214+
error_ref[] = ZlibError(zlib_error_message(zstream, code))
193215
return Δin, Δout, :error
194216
end
195217
end

Diff for: src/libz.jl

+31-7
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,17 @@ end
3939

4040
const Z_DEFAULT_COMPRESSION = Cint(-1)
4141

42-
const Z_OK = Cint(0)
43-
const Z_STREAM_END = Cint(1)
44-
const Z_BUF_ERROR = Cint(-5)
42+
const Z_OK = Cint(0)
43+
const Z_STREAM_END = Cint(1)
44+
const Z_NEED_DICT = Cint(2)
45+
const Z_ERRNO = Cint(-1)
46+
const Z_STREAM_ERROR = Cint(-2)
47+
const Z_DATA_ERROR = Cint(-3)
48+
const Z_MEM_ERROR = Cint(-4)
49+
const Z_BUF_ERROR = Cint(-5)
50+
const Z_VERSION_ERROR = Cint(-6)
51+
# Return codes for the compression/decompression functions. Negative values
52+
# are errors, positive values are used for special but normal events.
4553

4654
const Z_NO_FLUSH = Cint(0)
4755
const Z_SYNC_FLUSH = Cint(2)
@@ -59,7 +67,9 @@ function version()
5967
return unsafe_string(ccall((:zlibVersion, libz), Ptr{UInt8}, ()))
6068
end
6169

62-
const zlib_version = version()
70+
# This is the version of zlib used to make this wrapper.
71+
# The `_init!` functions will return an error if the library is not compatible.
72+
const zlib_version = "1.3.1"
6373

6474
function deflate_init!(zstream::ZStream, level::Integer, windowbits::Integer)
6575
return ccall((:deflateInit2_, libz), Cint, (Ref{ZStream}, Cint, Cint, Cint, Cint, Cint, Cstring, Cint), zstream, level, Z_DEFLATED, windowbits, #=default memlevel=#8, #=default strategy=#0, zlib_version, sizeof(ZStream))
@@ -93,14 +103,28 @@ function inflate!(zstream::ZStream, flush::Integer)
93103
return ccall((:inflate, libz), Cint, (Ref{ZStream}, Cint), zstream, flush)
94104
end
95105

106+
# Error
107+
# -----
108+
109+
struct ZlibError <: Exception
110+
msg::String
111+
end
112+
113+
function Base.showerror(io::IO, err::ZlibError)
114+
print(io, "ZlibError: ")
115+
print(io, err.msg)
116+
nothing
117+
end
118+
119+
96120
function zerror(zstream::ZStream, code::Integer)
97-
return throw(ErrorException(zlib_error_message(zstream, code)))
121+
throw(ZlibError(zlib_error_message(zstream, code)))
98122
end
99123

100124
function zlib_error_message(zstream::ZStream, code::Integer)
101125
if zstream.msg == C_NULL
102-
return "zlib error: <no message> (code: $(code))"
126+
return "<no message> (code: $(code))"
103127
else
104-
return "zlib error: $(unsafe_string(zstream.msg)) (code: $(code))"
128+
return "$(unsafe_string(zstream.msg)) (code: $(code))"
105129
end
106130
end

Diff for: test/runtests.jl

+33-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using CodecZlib
2+
using CodecZlib: ZlibError
23
using Test
34
using Aqua: Aqua
45
using TranscodingStreams:
@@ -56,7 +57,7 @@ const testdir = @__DIR__
5657
gzip_data_corrupted[1] = 0x00 # corrupt header
5758
file = IOBuffer(gzip_data_corrupted)
5859
stream = GzipDecompressorStream(file)
59-
@test_throws ErrorException read(stream)
60+
@test_throws ZlibError read(stream)
6061
@test_throws ArgumentError read(stream)
6162
@test !isopen(stream)
6263
@test isopen(file)
@@ -160,7 +161,7 @@ end
160161
close(stream)
161162

162163
stream = TranscodingStream(GzipDecompressor(gziponly=true), IOBuffer(zlib_data))
163-
@test_throws Exception read(stream)
164+
@test_throws ZlibError read(stream)
164165
close(stream)
165166

166167
file = IOBuffer(b"foo")
@@ -251,7 +252,7 @@ end
251252

252253
@testset "panic" begin
253254
stream = TranscodingStream(GzipDecompressor(), IOBuffer("some invalid data"))
254-
@test_throws ErrorException read(stream)
255+
@test_throws ZlibError read(stream)
255256
@test_throws ArgumentError eof(stream)
256257
end
257258

@@ -304,3 +305,32 @@ end
304305
@test_throws ArgumentError TranscodingStreams.stats(stream)
305306
end
306307
end
308+
309+
@testset "unexpected end of stream errors" begin
310+
tests = [
311+
(ZlibCompressor, ZlibDecompressor),
312+
(DeflateCompressor, DeflateDecompressor),
313+
(GzipCompressor, GzipDecompressor),
314+
]
315+
@testset "$(encoder)" for (encoder, decoder) in tests
316+
local uncompressed = rand(UInt8, 1000)
317+
local compressed = transcode(encoder, uncompressed)
318+
for i in 0:length(compressed)-1
319+
@test_throws ZlibError("the compressed stream may be truncated") transcode(decoder, compressed[1:i])
320+
end
321+
@test transcode(decoder, compressed) == uncompressed
322+
# compressing empty vector should still work
323+
@test transcode(decoder, transcode(encoder, UInt8[])) == UInt8[]
324+
end
325+
end
326+
@testset "data errors" begin
327+
@test_throws ZlibError transcode(ZlibDecompressor, zeros(UInt8, 10))
328+
local uncompressed = rand(UInt8, 1000)
329+
local compressed = transcode(ZlibCompressor, uncompressed)
330+
compressed[70] ⊻= 0x01
331+
@test_throws ZlibError transcode(ZlibDecompressor, compressed)
332+
end
333+
@testset "error printing" begin
334+
@test sprint(Base.showerror, ZlibError("test error message")) ==
335+
"ZlibError: test error message"
336+
end

0 commit comments

Comments
 (0)