zip_tricks 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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