-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
reader.cr
111 lines (90 loc) · 3.01 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
# A read-only `IO` object to decompress data in the zlib 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::Zlib::Reader < IO
include IO::Buffered
# Whether to close the enclosed `IO` when closing this reader.
property? sync_close = false
# Returns `true` if this reader is closed.
getter? closed = false
# Creates a new reader from the given *io*.
def initialize(@io : IO, @sync_close = false, dict : Bytes? = nil)
Compress::Zlib::Reader.read_header(io, dict)
@flate_io = Compress::Deflate::Reader.new(@io, dict: dict)
@adler32 = ::Digest::Adler32.initial
@end = false
end
# Creates a new reader from the given *io*, yields it to the given block,
# and closes it at the end.
def self.open(io : IO, sync_close = false, dict : Bytes? = nil)
reader = new(io, sync_close: sync_close, dict: dict)
yield reader ensure reader.close
end
protected def self.read_header(io, dict)
cmf = io.read_byte || invalid_header
cm = cmf & 0xF
if cm != 8 # the compression method must be 8
invalid_header
end
flg = io.read_byte || invalid_header
# CMF and FLG, when viewed as a 16-bit unsigned integer stored
# in MSB order (CMF*256 + FLG), must be a multiple of 31
unless (cmf.to_u16*256 + flg.to_u16).divisible_by?(31)
invalid_header
end
fdict = flg.bit(5) == 1
if fdict
unless dict
raise Compress::Zlib::Error.new("Missing dictionary")
end
checksum = io.read_bytes(UInt32, IO::ByteFormat::BigEndian)
dict_checksum = ::Digest::Adler32.checksum(dict)
if checksum != dict_checksum
raise Compress::Zlib::Error.new("Dictionary ADLER-32 checksum mismatch")
end
end
end
# See `IO#read`.
def unbuffered_read(slice : Bytes) : Int32
check_open
return 0 if slice.empty?
return 0 if @end
read_bytes = @flate_io.read(slice)
if read_bytes == 0
# Check ADLER-32
@end = true
@flate_io.close
adler32 = @io.read_bytes(UInt32, IO::ByteFormat::BigEndian)
if adler32 != @adler32
raise Compress::Zlib::Error.new("ADLER-32 checksum mismatch")
end
else
# Update ADLER-32 checksum
@adler32 = ::Digest::Adler32.update(slice[0, read_bytes], @adler32)
end
read_bytes
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::Zlib::Reader"
end
def unbuffered_flush : NoReturn
raise IO::Error.new "Can't flush Compress::Zlib::Reader"
end
def unbuffered_close : Nil
return if @closed
@closed = true
@flate_io.close
@io.close if @sync_close
end
def unbuffered_rewind : Nil
check_open
@io.rewind
initialize(@io, @sync_close, @flate_io.dict)
end
protected def self.invalid_header
raise Compress::Zlib::Error.new("Invalid header")
end
end