/
reader.cr
154 lines (127 loc) · 4.56 KB
/
reader.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# A read-only `IO` object to decompress data in the DEFLATE format.
#
# Instances of this class wrap another IO object. When you read from this instance
# instance, it reads data from the underlying IO, decompresses it, and returns
# it to the caller.
class Compress::Deflate::Reader < IO
include IO::Buffered
# If `#sync_close?` is `true`, closing this IO will close the underlying IO.
property? sync_close : Bool
# Returns `true` if this reader is closed.
getter? closed = false
# Dictionary passed in the constructor
getter dict : Bytes?
# Peeked bytes from the underlying IO
@peek : Bytes?
# Creates an instance of Flate::Reader.
def initialize(@io : IO, @sync_close : Bool = false, @dict : Bytes? = nil)
@buf = uninitialized UInt8[1] # input buffer used by zlib
@stream = LibZ::ZStream.new
@stream.zalloc = LibZ::AllocFunc.new { |opaque, items, size| GC.malloc(items * size) }
@stream.zfree = LibZ::FreeFunc.new { |opaque, address| GC.free(address) }
ret = LibZ.inflateInit2(pointerof(@stream), -LibZ::MAX_BITS, LibZ.zlibVersion, sizeof(LibZ::ZStream))
raise Compress::Deflate::Error.new(ret, @stream) unless ret.ok?
@peek = nil
@end = false
end
# Creates a new reader from the given *io*, yields it to the given block,
# and closes it at its end.
def self.open(io : IO, sync_close : Bool = false, dict : Bytes? = nil, &)
reader = new(io, sync_close: sync_close, dict: dict)
yield reader ensure reader.close
end
# Creates an instance of Flate::Reader for the gzip format.
# has written.
def self.gzip(input, sync_close : Bool = false) : self
new input, wbits: GZIP, sync_close: sync_close
end
# Creates an instance of Flate::Reader for the gzip format, yields it to the given block, and closes
# it at its end.
def self.gzip(input, sync_close : Bool = false, &)
reader = gzip input, sync_close: sync_close
yield reader ensure reader.close
end
# Always raises `IO::Error` because this is a read-only `IO`.
def unbuffered_write(slice : Bytes) : NoReturn
raise IO::Error.new "Can't write to Compress::Deflate::Reader"
end
# See `IO#read`.
def unbuffered_read(slice : Bytes) : Int32
check_open
return 0 if slice.empty?
return 0 if @end
while true
if @stream.avail_in == 0
# Try to peek into the underlying IO, so we can feed more
# data into zlib
@peek = @io.peek
if peek = @peek
@stream.next_in = peek
@stream.avail_in = peek.size
else
# If peeking is not possible, we are cautious and
# read byte per byte to avoid reading more data beyond
# the compressed data (for example, if the compressed stream
# is part of a zip/gzip file).
@stream.next_in = @buf.to_unsafe
@stream.avail_in = @io.read(@buf.to_slice).to_u32
end
end
old_avail_in = @stream.avail_in
@stream.avail_out = slice.size.to_u32
@stream.next_out = slice.to_unsafe
ret = LibZ.inflate(pointerof(@stream), LibZ::Flush::NO_FLUSH)
read_bytes = slice.size - @stream.avail_out
# If we were able to peek, skip the used bytes in the underlying IO
avail_in_diff = old_avail_in - @stream.avail_in
if @peek && avail_in_diff > 0
@io.skip(avail_in_diff)
end
case ret
when .need_dict?
if dict = @dict
ret = LibZ.inflateSetDictionary(pointerof(@stream), dict, dict.size)
next if ret.ok?
end
raise Compress::Deflate::Error.new(ret, @stream)
when .errno?,
.data_error?,
.mem_error?,
.buf_error?,
.version_error?
raise Compress::Deflate::Error.new(ret, @stream)
when .stream_end?
@end = true
return read_bytes
else
# LibZ.inflate might not write any data to the output slice because
# it might need more input. We can know this happened because *ret*
# is not STREAM_END.
if read_bytes == 0
next
else
return read_bytes
end
end
end
end
def unbuffered_flush : NoReturn
raise IO::Error.new "Can't flush Compress::Deflate::Reader"
end
# Closes this reader.
def unbuffered_close : Nil
return if @closed
@closed = true
ret = LibZ.inflateEnd(pointerof(@stream))
raise Compress::Deflate::Error.new(ret, @stream) unless ret.ok?
@io.close if @sync_close
end
def unbuffered_rewind : Nil
check_open
@io.rewind
initialize(@io, @sync_close, @dict)
end
def inspect(io : IO) : Nil
to_s(io)
end
end