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.
@@ -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