zip_tricks 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +20 -0
- data/README.md +145 -0
- data/Rakefile +51 -0
- data/lib/zip_tricks/block_deflate.rb +89 -0
- data/lib/zip_tricks/block_write.rb +40 -0
- data/lib/zip_tricks/manifest.rb +85 -0
- data/lib/zip_tricks/null_writer.rb +7 -0
- data/lib/zip_tricks/rack_body.rb +41 -0
- data/lib/zip_tricks/stored_size_estimator.rb +44 -0
- data/lib/zip_tricks/stream_crc32.rb +43 -0
- data/lib/zip_tricks/streamer.rb +175 -0
- data/lib/zip_tricks/write_and_tell.rb +33 -0
- data/lib/zip_tricks.rb +9 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/zip_tricks/block_deflate_spec.rb +111 -0
- data/spec/zip_tricks/block_write_spec.rb +95 -0
- data/spec/zip_tricks/manifest_spec.rb +60 -0
- data/spec/zip_tricks/rack_body_spec.rb +34 -0
- data/spec/zip_tricks/stored_size_estimator_spec.rb +22 -0
- data/spec/zip_tricks/stream_crc32_spec.rb +38 -0
- data/spec/zip_tricks/streamer_spec.rb +253 -0
- data/spec/zip_tricks/war-and-peace.txt +10810 -0
- data/spec/zip_tricks/write_and_tell_spec.rb +43 -0
- data/zip_tricks.gemspec +90 -0
- metadata +192 -0
@@ -0,0 +1,253 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
describe ZipTricks::Streamer do
|
6
|
+
let(:test_text_file_path) {
|
7
|
+
File.join(__dir__, 'war-and-peace.txt')
|
8
|
+
}
|
9
|
+
|
10
|
+
# Run each test in a temporady directory, and nuke it afterwards
|
11
|
+
around(:each) do |example|
|
12
|
+
wd = Dir.pwd
|
13
|
+
Dir.mktmpdir do | td |
|
14
|
+
Dir.chdir(td)
|
15
|
+
example.run
|
16
|
+
end
|
17
|
+
Dir.chdir(wd)
|
18
|
+
end
|
19
|
+
|
20
|
+
def rewind_after(*ios)
|
21
|
+
yield.tap { ios.map(&:rewind) }
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'raises an InvalidOutput if the given object does not support the methods' do
|
25
|
+
expect {
|
26
|
+
described_class.new(nil)
|
27
|
+
}.to raise_error(ZipTricks::Streamer::InvalidOutput)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns the position in the IO at every call' do
|
31
|
+
io = StringIO.new
|
32
|
+
zip = described_class.new(io)
|
33
|
+
pos = zip.add_compressed_entry('file.jpg', 182919, 8921, 8912)
|
34
|
+
expect(pos).to eq(io.tell)
|
35
|
+
expect(pos).to eq(38)
|
36
|
+
|
37
|
+
retval = zip << SecureRandom.random_bytes(8912)
|
38
|
+
expect(retval).to eq(zip)
|
39
|
+
expect(io.tell).to eq(8950)
|
40
|
+
|
41
|
+
pos = zip.add_stored_entry('file.jpg', 8921, 182919)
|
42
|
+
expect(pos).to eq(8988)
|
43
|
+
zip << SecureRandom.random_bytes(8921)
|
44
|
+
expect(io.tell).to eq(17909)
|
45
|
+
|
46
|
+
pos = zip.write_central_directory!
|
47
|
+
expect(pos).to eq(io.tell)
|
48
|
+
expect(pos).to eq(17985)
|
49
|
+
|
50
|
+
pos_after_close = zip.close
|
51
|
+
expect(pos_after_close).to eq(pos)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'can write and then read the block-deflated files' do
|
55
|
+
f = Tempfile.new('raw')
|
56
|
+
f.binmode
|
57
|
+
|
58
|
+
rewind_after(f) do
|
59
|
+
f << ('A' * 1024 * 1024)
|
60
|
+
f << SecureRandom.random_bytes(1248)
|
61
|
+
f << ('B' * 1024 * 1024)
|
62
|
+
end
|
63
|
+
|
64
|
+
crc = rewind_after(f) { Zlib.crc32(f.read) }
|
65
|
+
|
66
|
+
compressed_blockwise = StringIO.new
|
67
|
+
rewind_after(compressed_blockwise, f) do
|
68
|
+
ZipTricks::BlockDeflate.deflate_in_blocks_and_terminate(f, compressed_blockwise, block_size: 1024)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Perform the zipping
|
72
|
+
zip_file = Tempfile.new('z')
|
73
|
+
zip_file.binmode
|
74
|
+
|
75
|
+
described_class.open(zip_file) do |zip|
|
76
|
+
zip.add_compressed_entry("compressed-file.bin", f.size, crc, compressed_blockwise.size)
|
77
|
+
zip << compressed_blockwise.read
|
78
|
+
end
|
79
|
+
zip_file.flush
|
80
|
+
|
81
|
+
per_filename = {}
|
82
|
+
Zip::File.open(zip_file.path) do |zip_file|
|
83
|
+
# Handle entries one by one
|
84
|
+
zip_file.each do |entry|
|
85
|
+
# The entry name gets returned with a binary encoding, we have to force it back.
|
86
|
+
per_filename[entry.name] = entry.get_input_stream.read
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
expect(per_filename['compressed-file.bin'].bytesize).to eq(f.size)
|
91
|
+
expect(Digest::SHA1.hexdigest(per_filename['compressed-file.bin'])).to eq(Digest::SHA1.hexdigest(f.read))
|
92
|
+
|
93
|
+
output = `unzip -v #{zip_file.path}`
|
94
|
+
puts output.inspect
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
it 'creates an archive that OSX ArchiveUtility can handle' do
|
99
|
+
au_path = '/System/Library/CoreServices/Applications/Archive Utility.app/Contents/MacOS/Archive Utility'
|
100
|
+
unless File.exist?(au_path)
|
101
|
+
skip "This system does not have ArchiveUtility"
|
102
|
+
end
|
103
|
+
|
104
|
+
outbuf = Tempfile.new('zip')
|
105
|
+
outbuf.binmode
|
106
|
+
|
107
|
+
zip = ZipTricks::Streamer.new(outbuf)
|
108
|
+
|
109
|
+
File.open(test_text_file_path, 'rb') do | source_f |
|
110
|
+
crc32 = rewind_after(source_f) { Zlib.crc32(source_f.read) }
|
111
|
+
|
112
|
+
compressed_buffer = StringIO.new
|
113
|
+
|
114
|
+
expect(ZipTricks::BlockDeflate).to receive(:deflate_chunk).at_least(:twice).and_call_original
|
115
|
+
|
116
|
+
# Compress in blocks of 4 Kb
|
117
|
+
rewind_after(source_f, compressed_buffer) do
|
118
|
+
ZipTricks::BlockDeflate.deflate_in_blocks_and_terminate(source_f, compressed_buffer, block_size: 1024 * 4)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Add this file compressed...
|
122
|
+
zip.add_compressed_entry('war-and-peace.txt', source_f.size, crc32, compressed_buffer.size)
|
123
|
+
zip << compressed_buffer.string
|
124
|
+
|
125
|
+
# ...and stored.
|
126
|
+
zip.add_stored_entry('war-and-peace-raw.txt', source_f.size, crc32)
|
127
|
+
zip << source_f.read
|
128
|
+
|
129
|
+
zip.close
|
130
|
+
|
131
|
+
outbuf.flush
|
132
|
+
File.unlink('test.zip') rescue nil
|
133
|
+
File.rename(outbuf.path, 'osx-archive-test.zip')
|
134
|
+
|
135
|
+
# ArchiveUtility sometimes puts the stuff it unarchives in ~/Downloads etc. so do
|
136
|
+
# not perform any checks on the files since we do not really know where they are on disk.
|
137
|
+
# Visual inspection should show whether the unarchiving is handled correctly.
|
138
|
+
`#{Shellwords.join([au_path, 'osx-archive-test.zip'])}`
|
139
|
+
end
|
140
|
+
|
141
|
+
FileUtils.rm_rf('osx-archive-test')
|
142
|
+
FileUtils.rm_rf('osx-archive-test.zip')
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'archives files which can then be read using the usual means with Rubyzip' do
|
146
|
+
zip_buf = Tempfile.new('zipp')
|
147
|
+
zip_buf.binmode
|
148
|
+
output_io = double('IO')
|
149
|
+
|
150
|
+
# Only allow the methods we provide in BlockWrite.
|
151
|
+
# Will raise an error if other methods are triggered (the ones that
|
152
|
+
# might try to rewind the IO).
|
153
|
+
allow(output_io).to receive(:<<) {|data|
|
154
|
+
zip_buf << data.to_s.force_encoding(Encoding::BINARY)
|
155
|
+
}
|
156
|
+
|
157
|
+
allow(output_io).to receive(:tell) { zip_buf.tell }
|
158
|
+
allow(output_io).to receive(:pos) { zip_buf.pos }
|
159
|
+
allow(output_io).to receive(:close)
|
160
|
+
|
161
|
+
# Generate a couple of random files
|
162
|
+
raw_file_1 = SecureRandom.random_bytes(1024 * 20)
|
163
|
+
raw_file_2 = SecureRandom.random_bytes(1024 * 128)
|
164
|
+
|
165
|
+
# Perform the zipping
|
166
|
+
zip = described_class.new(output_io)
|
167
|
+
zip.add_stored_entry("first-file.bin", raw_file_1.size, Zlib.crc32(raw_file_1))
|
168
|
+
zip << raw_file_1
|
169
|
+
zip.add_stored_entry("second-file.bin", raw_file_2.size, Zlib.crc32(raw_file_2))
|
170
|
+
zip << raw_file_2
|
171
|
+
zip.close
|
172
|
+
|
173
|
+
zip_buf.flush
|
174
|
+
|
175
|
+
per_filename = {}
|
176
|
+
Zip::File.open(zip_buf.path) do |zip_file|
|
177
|
+
# Handle entries one by one
|
178
|
+
zip_file.each do |entry|
|
179
|
+
# The entry name gets returned with a binary encoding, we have to force it back.
|
180
|
+
# Somehow an empty string gets read
|
181
|
+
per_filename[entry.name] = entry.get_input_stream.read
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
expect(per_filename['first-file.bin'].unpack("C*")).to eq(raw_file_1.unpack("C*"))
|
186
|
+
expect(per_filename['second-file.bin'].unpack("C*")).to eq(raw_file_2.unpack("C*"))
|
187
|
+
|
188
|
+
wd = Dir.pwd
|
189
|
+
Dir.mktmpdir do | td |
|
190
|
+
Dir.chdir(td)
|
191
|
+
output = `unzip -v #{zip_buf.path}`
|
192
|
+
puts output.inspect
|
193
|
+
end
|
194
|
+
Dir.chdir(wd)
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'sets the general-purpose flag for entries with UTF8 names' do
|
198
|
+
zip_buf = Tempfile.new('zipp')
|
199
|
+
zip_buf.binmode
|
200
|
+
|
201
|
+
# Generate a couple of random files
|
202
|
+
raw_file_1 = SecureRandom.random_bytes(1024 * 20)
|
203
|
+
raw_file_2 = SecureRandom.random_bytes(1024 * 128)
|
204
|
+
|
205
|
+
# Perform the zipping
|
206
|
+
zip = described_class.new(zip_buf)
|
207
|
+
zip.add_stored_entry("first-file.bin", raw_file_1.size, Zlib.crc32(raw_file_1))
|
208
|
+
zip << raw_file_1
|
209
|
+
zip.add_stored_entry("второй-файл.bin", raw_file_2.size, Zlib.crc32(raw_file_2))
|
210
|
+
zip << raw_file_2
|
211
|
+
zip.close
|
212
|
+
|
213
|
+
zip_buf.flush
|
214
|
+
|
215
|
+
entries = []
|
216
|
+
Zip::File.open(zip_buf.path) do |zip_file|
|
217
|
+
# Handle entries one by one
|
218
|
+
zip_file.each {|entry| entries << entry }
|
219
|
+
first_entry, second_entry = entries
|
220
|
+
|
221
|
+
expect(first_entry.gp_flags).to eq(0)
|
222
|
+
expect(first_entry.name).to eq('first-file.bin')
|
223
|
+
|
224
|
+
# Rubyzip does not properly set the encoding of the entries it reads
|
225
|
+
expect(second_entry.gp_flags).to eq(2048)
|
226
|
+
expect(second_entry.name).to eq("второй-файл.bin".force_encoding(Encoding::BINARY))
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'raises when the actual bytes written for a stored entry does not match the entry header' do
|
231
|
+
expect {
|
232
|
+
ZipTricks::Streamer.open(StringIO.new) do | zip |
|
233
|
+
zip.add_stored_entry('file', 123, 0)
|
234
|
+
zip << 'xx'
|
235
|
+
end
|
236
|
+
}.to raise_error {|e|
|
237
|
+
expect(e).to be_kind_of(ZipTricks::Streamer::EntryBodySizeMismatch)
|
238
|
+
expect(e.message).to eq('Wrong number of bytes written for entry (expected 123, got 2)')
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'raises when the actual bytes written for a compressed entry does not match the entry header' do
|
243
|
+
expect {
|
244
|
+
ZipTricks::Streamer.open(StringIO.new) do | zip |
|
245
|
+
zip.add_compressed_entry('file', 1898121, 0, 123)
|
246
|
+
zip << 'xx'
|
247
|
+
end
|
248
|
+
}.to raise_error {|e|
|
249
|
+
expect(e).to be_kind_of(ZipTricks::Streamer::EntryBodySizeMismatch)
|
250
|
+
expect(e.message).to eq('Wrong number of bytes written for entry (expected 123, got 2)')
|
251
|
+
}
|
252
|
+
end
|
253
|
+
end
|