zip_tricks 2.8.1 → 3.0.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -3
  3. data/IMPLEMENTATION_DETAILS.md +2 -10
  4. data/README.md +62 -59
  5. data/examples/archive_size_estimate.rb +4 -4
  6. data/examples/rack_application.rb +3 -5
  7. data/lib/zip_tricks/block_deflate.rb +21 -0
  8. data/lib/zip_tricks/file_reader.rb +491 -0
  9. data/lib/zip_tricks/null_writer.rb +7 -2
  10. data/lib/zip_tricks/rack_body.rb +3 -3
  11. data/lib/zip_tricks/remote_io.rb +30 -20
  12. data/lib/zip_tricks/remote_uncap.rb +10 -10
  13. data/lib/zip_tricks/size_estimator.rb +64 -0
  14. data/lib/zip_tricks/stream_crc32.rb +2 -2
  15. data/lib/zip_tricks/streamer/deflated_writer.rb +26 -0
  16. data/lib/zip_tricks/streamer/entry.rb +21 -0
  17. data/lib/zip_tricks/streamer/stored_writer.rb +25 -0
  18. data/lib/zip_tricks/streamer/writable.rb +20 -0
  19. data/lib/zip_tricks/streamer.rb +172 -66
  20. data/lib/zip_tricks/zip_writer.rb +346 -0
  21. data/lib/zip_tricks.rb +1 -4
  22. data/spec/spec_helper.rb +1 -38
  23. data/spec/zip_tricks/file_reader_spec.rb +47 -0
  24. data/spec/zip_tricks/rack_body_spec.rb +2 -2
  25. data/spec/zip_tricks/remote_io_spec.rb +8 -20
  26. data/spec/zip_tricks/remote_uncap_spec.rb +4 -4
  27. data/spec/zip_tricks/size_estimator_spec.rb +31 -0
  28. data/spec/zip_tricks/streamer_spec.rb +59 -36
  29. data/spec/zip_tricks/zip_writer_spec.rb +408 -0
  30. data/zip_tricks.gemspec +20 -14
  31. metadata +33 -16
  32. data/lib/zip_tricks/manifest.rb +0 -85
  33. data/lib/zip_tricks/microzip.rb +0 -339
  34. data/lib/zip_tricks/stored_size_estimator.rb +0 -44
  35. data/spec/zip_tricks/manifest_spec.rb +0 -60
  36. data/spec/zip_tricks/microzip_interop_spec.rb +0 -48
  37. data/spec/zip_tricks/microzip_spec.rb +0 -546
  38. data/spec/zip_tricks/stored_size_estimator_spec.rb +0 -22
@@ -1,48 +0,0 @@
1
- require_relative '../spec_helper'
2
-
3
- describe 'Microzip in interop context' do
4
- let(:described_class) { ZipTricks::Microzip}
5
-
6
- it 'creates an archive that can be opened by Rubyzip, with a small number of very tiny text files' do
7
- tf = ManagedTempfile.new('zip')
8
- z = described_class.new
9
-
10
- test_str = Random.new.bytes(64)
11
- crc = Zlib.crc32(test_str)
12
- t = Time.now.utc
13
-
14
- 3.times do |i|
15
- fn = "test-#{i}"
16
- z.add_local_file_header(io: tf, filename: fn, crc32: crc, compressed_size: test_str.bytesize,
17
- uncompressed_size: test_str.bytesize, storage_mode: 0, mtime: t)
18
- tf << test_str
19
- end
20
- z.write_central_directory(tf)
21
- tf.flush
22
-
23
- Zip::File.open(tf.path) do |zip_file|
24
- entries = zip_file.to_a
25
- expect(entries.length).to eq(3)
26
- entries.each do |entry|
27
- # Make sure it is tagged as UNIX
28
- expect(entry.fstype).to eq(3)
29
-
30
- # Check the file contents
31
- readback = entry.get_input_stream.read
32
- readback.force_encoding(Encoding::BINARY)
33
- expect(readback).to eq(test_str)
34
-
35
- # The CRC
36
- expect(entry.crc).to eq(crc)
37
-
38
- # Check the name
39
- expect(entry.name).to match(/test/)
40
-
41
- # Check the right external attributes (non-executable on UNIX)
42
- expect(entry.external_file_attributes).to eq(2175008768)
43
- end
44
- end
45
-
46
- inspect_zip_with_external_tool(tf.path)
47
- end
48
- end
@@ -1,546 +0,0 @@
1
- require_relative '../spec_helper'
2
- require_relative '../../testing/support'
3
-
4
- describe ZipTricks::Microzip do
5
- class ByteReader < Struct.new(:io)
6
- def read_2b
7
- io.read(2).unpack('v').first
8
- end
9
-
10
- def read_2c
11
- io.read(2).unpack('CC').first
12
- end
13
-
14
- def read_4b
15
- io.read(4).unpack('V').first
16
- end
17
-
18
- def read_8b
19
- io.read(8).unpack('Q<').first
20
- end
21
-
22
- def read_n(n)
23
- io.read(n)
24
- end
25
- end
26
-
27
- class IOWrapper < ZipTricks::WriteAndTell
28
- def read(n)
29
- @io.read(n)
30
- end
31
- end
32
-
33
- it 'raises an exception if the filename is non-unique in the already existing set' do
34
- z = described_class.new
35
- z.add_local_file_header(io: StringIO.new, filename: 'foo.txt', crc32: 0, compressed_size: 0, uncompressed_size: 0, storage_mode: 0)
36
- expect {
37
- z.add_local_file_header(io: StringIO.new, filename: 'foo.txt', crc32: 0, compressed_size: 0, uncompressed_size: 0, storage_mode: 0)
38
- }.to raise_error(/already/)
39
- end
40
-
41
- it 'raises an exception if the filename contains backward slashes' do
42
- z = described_class.new
43
- expect {
44
- z.add_local_file_header(io: StringIO.new, filename: 'windows\not\welcome.txt',
45
- crc32: 0, compressed_size: 0, uncompressed_size: 0, storage_mode: 0)
46
- }.to raise_error(/UNIX/)
47
- end
48
-
49
- it 'raises an exception if the filename does not fit in 0xFFFF bytes' do
50
- longest_filename_in_the_universe = "x" * (0xFFFF + 1)
51
- z = described_class.new
52
- expect {
53
- z.add_local_file_header(io: StringIO.new, filename: longest_filename_in_the_universe,
54
- crc32: 0, compressed_size: 0, uncompressed_size: 0, storage_mode: 0)
55
- }.to raise_error(/is too long/)
56
- end
57
-
58
- describe '#add_local_file_header' do
59
- it 'writes out the local file header for an entry that fits into a standard ZIP' do
60
- buf = StringIO.new
61
- zip = described_class.new
62
- mtime = Time.utc(2016, 7, 17, 13, 48)
63
- zip.add_local_file_header(io: buf, filename: 'first-file.bin', crc32: 123, compressed_size: 8981,
64
- uncompressed_size: 90981, storage_mode: 8, mtime: mtime)
65
-
66
- buf.rewind
67
- br = ByteReader.new(buf)
68
- expect(br.read_4b).to eq(0x04034b50) # Signature
69
- expect(br.read_2b).to eq(20) # Version needed to extract
70
- expect(br.read_2b).to eq(0) # gp flags
71
- expect(br.read_2b).to eq(8) # storage mode
72
- expect(br.read_2b).to eq(28160) # DOS time
73
- expect(br.read_2b).to eq(18673) # DOS date
74
- expect(br.read_4b).to eq(123) # CRC32
75
- expect(br.read_4b).to eq(8981) # compressed size
76
- expect(br.read_4b).to eq(90981) # uncompressed size
77
- expect(br.read_2b).to eq('first-file.bin'.bytesize) # byte length of the filename
78
- expect(br.read_2b).to be_zero # size of extra fields
79
- expect(br.read_n('first-file.bin'.bytesize)).to eq('first-file.bin') # the filename
80
- expect(buf).to be_eof
81
- end
82
-
83
- it 'writes out the local file header for an entry with a UTF-8 filename, setting the proper GP flag bit' do
84
- buf = StringIO.new
85
- zip = described_class.new
86
- mtime = Time.utc(2016, 7, 17, 13, 48)
87
- zip.add_local_file_header(io: buf, filename: 'файл.bin', crc32: 123, compressed_size: 8981,
88
- uncompressed_size: 90981, storage_mode: 8, mtime: mtime)
89
-
90
- buf.rewind
91
- br = ByteReader.new(buf)
92
- br.read_4b # Signature
93
- br.read_2b # Version needed to extract
94
- expect(br.read_2b).to eq(2048) # gp flags
95
- end
96
-
97
- it "correctly recognizes UTF-8 filenames even if they are tagged as ASCII" do
98
- name = 'файл.bin'
99
- name.force_encoding(Encoding::US_ASCII)
100
-
101
- buf = StringIO.new
102
- zip = described_class.new
103
- mtime = Time.utc(2016, 7, 17, 13, 48)
104
- zip.add_local_file_header(io: buf, filename: name, crc32: 123, compressed_size: 8981,
105
- uncompressed_size: 90981, storage_mode: 8, mtime: mtime)
106
-
107
- buf.rewind
108
- br = ByteReader.new(buf)
109
- br.read_4b # Signature
110
- br.read_2b # Version needed to extract
111
- expect(br.read_2b).to eq(2048) # gp flags
112
- end
113
-
114
- it 'writes out the local file header for an entry with a filename with diacritics, setting the proper GP flag bit' do
115
- buf = StringIO.new
116
- zip = described_class.new
117
- mtime = Time.utc(2016, 7, 17, 13, 48)
118
- zip.add_local_file_header(io: buf, filename: 'Kungälv', crc32: 123, compressed_size: 8981,
119
- uncompressed_size: 90981, storage_mode: 8, mtime: mtime)
120
-
121
- buf.rewind
122
- br = ByteReader.new(buf)
123
- br.read_4b # Signature
124
- br.read_2b # Version needed to extract
125
- expect(br.read_2b).to eq(2048) # gp flags
126
- br.read_2b
127
- br.read_2b
128
- br.read_2b
129
- br.read_4b
130
- br.read_4b
131
- br.read_4b
132
- br.read_2b
133
- br.read_2b
134
- filename_readback = br.read_n('Kungälv'.bytesize)
135
- expect(filename_readback.force_encoding(Encoding::UTF_8)).to eq('Kungälv')
136
- end
137
-
138
- it 'writes out the local file header for an entry that requires Zip64 based on its compressed size _only_' do
139
- buf = StringIO.new
140
- zip = described_class.new
141
- mtime = Time.utc(2016, 7, 17, 13, 48)
142
- zip.add_local_file_header(io: buf, filename: 'first-file.bin', crc32: 123, compressed_size: (0xFFFFFFFF + 1),
143
- uncompressed_size: 90981, storage_mode: 8, mtime: mtime)
144
-
145
- buf.rewind
146
- br = ByteReader.new(buf)
147
- expect(br.read_4b).to eq(0x04034b50) # Signature
148
- expect(br.read_2b).to eq(45) # Version needed to extract (require Zip64 support)
149
- expect(br.read_2b).to eq(0) # gp flags
150
- expect(br.read_2b).to eq(8) # storage mode
151
- expect(br.read_2b).to eq(28160) # DOS time
152
- expect(br.read_2b).to eq(18673) # DOS date
153
- expect(br.read_4b).to eq(123) # CRC32
154
- expect(br.read_4b).to eq(0xFFFFFFFF) # compressed size (blanked out)
155
- expect(br.read_4b).to eq(0xFFFFFFFF) # uncompressed size (blanked out)
156
- expect(br.read_2b).to eq('first-file.bin'.bytesize) # byte length of the filename
157
- expect(br.read_2b).to eq(20) # size of extra fields
158
- expect(br.read_n('first-file.bin'.bytesize)).to eq('first-file.bin') # the filename
159
- expect(br.read_2b).to eq(1) # Zip64 extra field signature
160
- expect(br.read_2b).to eq(16) # Size of the Zip64 extra field
161
- expect(br.read_8b).to eq(90981) # True compressed size
162
- expect(br.read_8b).to eq(0xFFFFFFFF + 1) # True uncompressed size
163
- expect(buf).to be_eof
164
- end
165
-
166
- it 'writes out the local file header for an entry that requires Zip64 based on its uncompressed size _only_' do
167
- buf = StringIO.new
168
- zip = described_class.new
169
- mtime = Time.utc(2016, 7, 17, 13, 48)
170
- zip.add_local_file_header(io: buf, filename: 'first-file.bin', crc32: 123, compressed_size: 90981,
171
- uncompressed_size: (0xFFFFFFFF + 1), storage_mode: 8, mtime: mtime)
172
-
173
- buf.rewind
174
- br = ByteReader.new(buf)
175
- expect(br.read_4b).to eq(0x04034b50) # Signature
176
- expect(br.read_2b).to eq(45) # Version needed to extract (require Zip64 support)
177
- expect(br.read_2b).to eq(0) # gp flags
178
- expect(br.read_2b).to eq(8) # storage mode
179
- expect(br.read_2b).to eq(28160) # DOS time
180
- expect(br.read_2b).to eq(18673) # DOS date
181
- expect(br.read_4b).to eq(123) # CRC32
182
- expect(br.read_4b).to eq(0xFFFFFFFF) # compressed size (blanked out)
183
- expect(br.read_4b).to eq(0xFFFFFFFF) # uncompressed size (blanked out)
184
- expect(br.read_2b).to eq('first-file.bin'.bytesize) # byte length of the filename
185
- expect(br.read_2b).to eq(20) # size of extra fields
186
- expect(br.read_n('first-file.bin'.bytesize)).to eq('first-file.bin') # the filename
187
- expect(br.read_2b).to eq(1) # Zip64 extra field signature
188
- expect(br.read_2b).to eq(16) # Size of the Zip64 extra field
189
- expect(br.read_8b).to eq(0xFFFFFFFF + 1) # True uncompressed size
190
- expect(br.read_8b).to eq(90981) # True compressed size
191
- expect(buf).to be_eof
192
- end
193
-
194
- it 'does not write out the Zip64 extra if the position in the destination IO is beyond the Zip64 size limit' do
195
- buf = StringIO.new
196
- zip = described_class.new
197
- mtime = Time.utc(2016, 7, 17, 13, 48)
198
- expect(buf).to receive(:tell).and_return(0xFFFFFFFF + 1)
199
- zip.add_local_file_header(io: buf, filename: 'first-file.bin', crc32: 123, compressed_size: 123,
200
- uncompressed_size: 456, storage_mode: 8, mtime: mtime)
201
-
202
- buf.rewind
203
- br = ByteReader.new(buf)
204
- expect(br.read_4b).to eq(0x04034b50) # Signature
205
- expect(br.read_2b).to eq(20) # Version needed to extract (require Zip64 support)
206
- br.read_2b
207
- br.read_2b
208
- br.read_2b
209
- br.read_2b
210
- br.read_4b
211
- br.read_4b
212
- br.read_4b
213
- br.read_2b
214
- expect(br.read_2b).to be_zero
215
- end
216
- end
217
-
218
- describe '#write_central_directory' do
219
- it 'writes the central directory and makes it a valid one even if there were no files' do
220
- buf = StringIO.new
221
-
222
- zip = described_class.new
223
- zip.write_central_directory(buf)
224
-
225
- buf.rewind
226
- br = ByteReader.new(buf)
227
- expect(br.read_4b).to eq(0x06054b50) # EOCD signature
228
- expect(br.read_2b).to eq(0) # disk number
229
- expect(br.read_2b).to eq(0) # disk number of the disk containing EOCD
230
- expect(br.read_2b).to eq(0) # num files in the central directory of this disk
231
- expect(br.read_2b).to eq(0) # num files in the central directories of all disks
232
- expect(br.read_4b).to eq(0) # central directorys size
233
- expect(br.read_4b).to eq(0) # offset of start of central directory from the beginning of the disk
234
- expect(br.read_2b).to eq(0) # ZIP file comment length
235
- expect(buf).to be_eof
236
- end
237
-
238
- it 'writes the central directory for 2 files' do
239
- zip = described_class.new
240
-
241
- mtime = Time.utc(2016, 7, 17, 13, 48)
242
-
243
- buf = StringIO.new
244
- zip.add_local_file_header(io: buf, filename: 'first-file.bin', crc32: 123, compressed_size: 5,
245
- uncompressed_size: 8, storage_mode: 8, mtime: mtime)
246
- buf << Random.new.bytes(5)
247
- zip.add_local_file_header(io: buf, filename: 'second-file.txt', crc32: 546, compressed_size: 9,
248
- uncompressed_size: 9, storage_mode: 0, mtime: mtime)
249
- buf << Random.new.bytes(5)
250
-
251
- central_dir_offset = buf.tell
252
- zip.write_central_directory(buf)
253
-
254
- # Seek to where the central directory begins
255
- buf.rewind
256
- buf.seek(central_dir_offset)
257
-
258
- br = ByteReader.new(buf)
259
-
260
- # Central directory entry for the first file
261
- expect(br.read_4b).to eq(0x02014b50) # Central directory entry sig
262
- expect(br.read_2b).to eq(820) # version made by
263
- expect(br.read_2b).to eq(20) # version need to extract
264
- expect(br.read_2b).to eq(0) # general purpose bit flag
265
- expect(br.read_2b).to eq(8) # compression method (deflated here)
266
- expect(br.read_2b).to eq(28160) # last mod file time
267
- expect(br.read_2b).to eq(18673) # last mod file date
268
- expect(br.read_4b).to eq(123) # crc32
269
- expect(br.read_4b).to eq(5) # compressed size
270
- expect(br.read_4b).to eq(8) # uncompressed size
271
- expect(br.read_2b).to eq(14) # filename length
272
- expect(br.read_2b).to eq(0) # extra field length
273
- expect(br.read_2b).to eq(0) # file comment
274
- expect(br.read_2b).to eq(0) # disk number, must be blanked to the maximum value because of The Unarchiver bug
275
- expect(br.read_2b).to eq(0) # internal file attributes
276
- expect(br.read_4b).to eq(2175008768) # external file attributes
277
- expect(br.read_4b).to eq(0) # relative offset of local header
278
- expect(br.read_n(14)).to eq('first-file.bin') # the filename
279
-
280
- # Central directory entry for the second file
281
- expect(br.read_4b).to eq(0x02014b50) # Central directory entry sig
282
- expect(br.read_2b).to eq(820) # version made by
283
- expect(br.read_2b).to eq(20) # version need to extract
284
- expect(br.read_2b).to eq(0) # general purpose bit flag
285
- expect(br.read_2b).to eq(0) # compression method (stored here)
286
- expect(br.read_2b).to eq(28160) # last mod file time
287
- expect(br.read_2b).to eq(18673) # last mod file date
288
- expect(br.read_4b).to eq(546) # crc32
289
- expect(br.read_4b).to eq(9) # compressed size
290
- expect(br.read_4b).to eq(9) # uncompressed size
291
- expect(br.read_2b).to eq('second-file.bin'.bytesize) # filename length
292
- expect(br.read_2b).to eq(0) # extra field length
293
- expect(br.read_2b).to eq(0) # file comment
294
- expect(br.read_2b).to eq(0) # disk number, must be blanked to the maximum value because of The Unarchiver bug
295
- expect(br.read_2b).to eq(0) # internal file attributes
296
- expect(br.read_4b).to eq(2175008768) # external file attributes
297
- expect(br.read_4b).to eq(49) # relative offset of local header
298
- expect(br.read_n('second-file.txt'.bytesize)).to eq('second-file.txt') # the filename
299
-
300
- expect(br.read_4b).to eq(0x06054b50) # end of central dir signature
301
- br.read_2b
302
- br.read_2b
303
- br.read_2b
304
- br.read_2b
305
- br.read_4b
306
- br.read_4b
307
- br.read_2b
308
-
309
- expect(buf).to be_eof
310
- end
311
-
312
- it 'writes the central directory for 1 file that is larger than 4GB' do
313
- zip = described_class.new
314
- buf = StringIO.new
315
- big = 0xFFFFFFFF + 2048
316
- mtime = Time.utc(2016, 7, 17, 13, 48)
317
-
318
- zip.add_local_file_header(io: buf, filename: 'big-file.bin', crc32: 12345, compressed_size: big,
319
- uncompressed_size: big, storage_mode: 0, mtime: mtime)
320
-
321
- central_dir_offset = buf.tell
322
-
323
- zip.write_central_directory(buf)
324
-
325
- # Seek to where the central directory begins
326
- buf.rewind
327
- buf.seek(central_dir_offset)
328
-
329
- br = ByteReader.new(buf)
330
-
331
- # Standard central directory entry (similar to the local file header)
332
- expect(br.read_4b).to eq(0x02014b50) # Central directory entry sig
333
- expect(br.read_2b).to eq(820) # version made by
334
- expect(br.read_2b).to eq(45) # version need to extract (45 for Zip64)
335
- expect(br.read_2b).to eq(0) # general purpose bit flag
336
- expect(br.read_2b).to eq(0) # compression method (stored here)
337
- expect(br.read_2b).to eq(28160) # last mod file time
338
- expect(br.read_2b).to eq(18673) # last mod file date
339
- expect(br.read_4b).to eq(12345) # crc32
340
- expect(br.read_4b).to eq(0xFFFFFFFF) # compressed size
341
- expect(br.read_4b).to eq(0xFFFFFFFF) # uncompressed size
342
- expect(br.read_2b).to eq(12) # filename length
343
- expect(br.read_2b).to eq(32) # extra field length (we store the Zip64 extra field for this file)
344
- expect(br.read_2b).to eq(0) # file comment
345
- expect(br.read_2b).to eq(0xFFFF) # disk number, must be blanked to the maximum value because of The Unarchiver bug
346
- expect(br.read_2b).to eq(0) # internal file attributes
347
- expect(br.read_4b).to eq(2175008768) # external file attributes
348
- expect(br.read_4b).to eq(0xFFFFFFFF) # relative offset of local header
349
- expect(br.read_n(12)).to eq('big-file.bin') # the filename
350
-
351
- # Zip64 extra field
352
- expect(br.read_2b).to eq(0x0001) # Tag for the "extra" block
353
- expect(br.read_2b).to eq(28) # Size of this "extra" block. For us it will always be 28
354
- expect(br.read_8b).to eq(big) # Original uncompressed file size
355
- expect(br.read_8b).to eq(big) # Original compressed file size
356
- expect(br.read_8b).to eq(0) # Offset of local header record
357
- expect(br.read_4b).to eq(0) # Number of the disk on which this file starts
358
- end
359
-
360
- it 'writes the central directory for 2 files which, together, make the central directory start beyound the 4GB threshold' do
361
- zip = described_class.new
362
- raw_buf = StringIO.new
363
-
364
- zip_write_buf = IOWrapper.new(raw_buf)
365
- big1 = 0xFFFFFFFF/2 + 512
366
- big2 = 0xFFFFFFFF/2 + 1024
367
- mtime = Time.utc(2016, 7, 17, 13, 48)
368
-
369
- zip.add_local_file_header(io: zip_write_buf, filename: 'first-big-file.bin', crc32: 12345, compressed_size: big1,
370
- uncompressed_size: big1, storage_mode: 0, mtime: mtime)
371
- zip_write_buf.advance_position_by(big1)
372
-
373
- zip.add_local_file_header(io: zip_write_buf, filename: 'second-big-file.bin', crc32: 54321, compressed_size: big2,
374
- uncompressed_size: big2, storage_mode: 0, mtime: mtime)
375
- zip_write_buf.advance_position_by(big2)
376
-
377
- fake_central_dir_offset = zip_write_buf.tell # Grab the position in the underlying buffer
378
- actual_central_dir_offset = raw_buf.tell # Grab the position in the underlying buffer
379
-
380
- zip.write_central_directory(zip_write_buf)
381
-
382
- # Seek to where the central directory begins
383
- raw_buf.seek(actual_central_dir_offset, IO::SEEK_SET)
384
-
385
- br = ByteReader.new(raw_buf)
386
-
387
- # Standard central directory entry (similar to the local file header)
388
- expect(br.read_4b).to eq(0x02014b50) # Central directory entry sig
389
- expect(br.read_2b).to eq(820) # version made by
390
- expect(br.read_2b).to eq(20) # version need to extract (45 for Zip64)
391
- expect(br.read_2b).to eq(0) # general purpose bit flag
392
- expect(br.read_2b).to eq(0) # compression method (stored here)
393
- expect(br.read_2b).to eq(28160) # last mod file time
394
- expect(br.read_2b).to eq(18673) # last mod file date
395
- expect(br.read_4b).to eq(12345) # crc32
396
- expect(br.read_4b).to eq(2147484159) # compressed size
397
- expect(br.read_4b).to eq(2147484159) # uncompressed size
398
- expect(br.read_2b).to eq(18) # filename length
399
- expect(br.read_2b).to eq(0) # extra field length
400
- expect(br.read_2b).to eq(0) # file comment length
401
- expect(br.read_2b).to eq(0) # disk number, must be blanked to the maximum value because of The Unarchiver bug
402
- expect(br.read_2b).to eq(0) # internal file attributes
403
- expect(br.read_4b).to eq(2175008768) # external file attributes
404
- expect(br.read_4b).to eq(0) # relative offset of local header
405
- expect(br.read_n(18)).to eq("first-big-file.bin") # the filename
406
-
407
- # Standard central directory entry (similar to the local file header)
408
- expect(br.read_4b).to eq(0x02014b50) # Central directory entry sig
409
- expect(br.read_2b).to eq(820) # version made by
410
- expect(br.read_2b).to eq(20) # version need to extract (45 for Zip64)
411
- expect(br.read_2b).to eq(0) # general purpose bit flag
412
- expect(br.read_2b).to eq(0) # compression method (stored here)
413
- expect(br.read_2b).to eq(28160) # last mod file time
414
- expect(br.read_2b).to eq(18673) # last mod file date
415
- expect(br.read_4b).to eq(54321) # crc32
416
- expect(br.read_4b).to eq(2147484671) # compressed size
417
- expect(br.read_4b).to eq(2147484671) # uncompressed size
418
- expect(br.read_2b).to eq(19) # filename length
419
- expect(br.read_2b).to eq(0) # extra field length
420
- expect(br.read_2b).to eq(0) # file comment length
421
- expect(br.read_2b).to eq(0) # disk number, must be blanked to the maximum value because of The Unarchiver bug
422
- expect(br.read_2b).to eq(0) # internal file attributes
423
- expect(br.read_4b).to eq(2175008768) # external file attributes
424
- expect(br.read_4b).to eq(2147484207) # relative offset of local header
425
- expect(br.read_n(19)).to eq('second-big-file.bin') # the filename
426
-
427
- # zip64 specific values for a whole central directory
428
- expect(br.read_4b).to eq(0x06064b50) # zip64 end of central dir signature
429
- expect(br.read_8b).to eq(44) # size of zip64 end of central directory record
430
- expect(br.read_2b).to eq(820) # version made by
431
- expect(br.read_2b).to eq(45) # version need to extract
432
- expect(br.read_4b).to eq(0) # number of this disk
433
- expect(br.read_4b).to eq(0) # another number related to disk
434
- expect(br.read_8b).to eq(2) # total number of entries in the central directory on this disk
435
- expect(br.read_8b).to eq(2) # total number of entries in the central directory
436
- expect(br.read_8b).to eq(129) # size of central directory
437
- expect(br.read_8b).to eq(4294968927) # starting disk number
438
- expect(br.read_4b).to eq(0x07064b50) # zip64 end of central dir locator signature
439
- expect(br.read_4b).to eq(0) # number of disk ...
440
- expect(br.read_8b).to eq(4294969056) # relative offset zip64
441
- expect(br.read_4b).to eq(1) # total number of disks
442
- end
443
-
444
- it 'writes the central directory for 3 files which, the third of which will require the Zip64 extra since it is past the 4GB offset' do
445
- zip = described_class.new
446
- raw_buf = StringIO.new
447
-
448
- zip_write_buf = IOWrapper.new(raw_buf)
449
- big1 = 0xFFFFFFFF/2 + 512
450
- big2 = 0xFFFFFFFF/2 + 1024
451
- big3 = 0xFFFFFFFF/2 + 1024
452
- mtime = Time.utc(2016, 7, 17, 13, 48)
453
-
454
- zip.add_local_file_header(io: zip_write_buf, filename: 'one', crc32: 12345, compressed_size: big1,
455
- uncompressed_size: big1, storage_mode: 0, mtime: mtime)
456
- zip_write_buf.advance_position_by(big1)
457
-
458
- zip.add_local_file_header(io: zip_write_buf, filename: 'two', crc32: 54321, compressed_size: big2,
459
- uncompressed_size: big2, storage_mode: 0, mtime: mtime)
460
- zip_write_buf.advance_position_by(big2)
461
-
462
- big3_offset = zip_write_buf.tell
463
-
464
- zip.add_local_file_header(io: zip_write_buf, filename: 'three', crc32: 54321, compressed_size: big2,
465
- uncompressed_size: big2, storage_mode: 0, mtime: mtime)
466
- zip_write_buf.advance_position_by(big3)
467
-
468
- fake_central_dir_offset = zip_write_buf.tell # Grab the position in the underlying buffer
469
- actual_central_dir_offset = raw_buf.tell # Grab the position in the underlying buffer
470
-
471
- zip.write_central_directory(zip_write_buf)
472
-
473
- # Seek to where the central directory begins
474
- raw_buf.seek(actual_central_dir_offset, IO::SEEK_SET)
475
-
476
- br = ByteReader.new(raw_buf)
477
-
478
- # Standard central directory entry (similar to the local file header)
479
- # Skip over two entries, because the other example has a 1-to-1 repeat of this
480
- 2.times {
481
- br.read_4b
482
- br.read_2b
483
- br.read_2b
484
- br.read_2b
485
- br.read_2b
486
- br.read_2b
487
- br.read_2b
488
- br.read_4b
489
- br.read_4b
490
- br.read_4b
491
- br.read_2b
492
- br.read_2b
493
- br.read_2b
494
- br.read_2b
495
- br.read_2b
496
- br.read_4b
497
- br.read_4b
498
- br.read_n(3)
499
- }
500
-
501
- # Entry for the third file DOES bear the Zip64 extra field
502
- expect(br.read_4b).to eq(0x02014b50) # Central directory entry sig
503
- expect(br.read_2b).to eq(820) # version made by
504
- expect(br.read_2b).to eq(45) # version need to extract (45 for Zip64) - this entry requires it
505
- expect(br.read_2b).to eq(0) # general purpose bit flag
506
- expect(br.read_2b).to eq(0) # compression method (stored here)
507
- expect(br.read_2b).to eq(28160) # last mod file time
508
- expect(br.read_2b).to eq(18673) # last mod file date
509
- expect(br.read_4b).to eq(54321) # crc32
510
- expect(br.read_4b).to eq(0xFFFFFFFF) # compressed size - blanked for Zip64
511
- expect(br.read_4b).to eq(0xFFFFFFFF) # uncompressed size - blanked for Zip64
512
- expect(br.read_2b).to eq(5) # filename length
513
- expect(br.read_2b).to eq(32) # extra field length (length of the ZIp64 extra)
514
- expect(br.read_2b).to eq(0) # file comment length
515
- expect(br.read_2b).to eq(0xFFFF) # disk number, with Zip64 must be blanked to the maximum value because of The Unarchiver bug
516
- expect(br.read_2b).to eq(0) # internal file attributes
517
- expect(br.read_4b).to eq(2175008768) # external file attributes
518
- expect(br.read_4b).to eq(4294967295) # relative offset of local header
519
- expect(br.read_n(5)).to eq('three') # the filename
520
- # then the Zip64 extra for that last file _only_
521
- expect(br.read_2b).to eq(0x0001) # Tag for the "extra" block
522
- expect(br.read_2b).to eq(28) # Size of this "extra" block. For us it will always be 28
523
- expect(br.read_8b).to eq(big3) # Original uncompressed file size
524
- expect(br.read_8b).to eq(big3) # Original compressed file size
525
- expect(br.read_8b).to eq(big3_offset) # Offset of local header record
526
- expect(br.read_4b).to eq(0) # Number of the disk on which this file starts
527
-
528
- # zip64 specific values for a whole central directory
529
- expect(br.read_4b).to eq(0x06064b50) # zip64 end of central dir signature
530
- expect(br.read_8b).to eq(44) # size of zip64 end of central directory record
531
- expect(br.read_2b).to eq(820) # version made by
532
- expect(br.read_2b).to eq(45) # version need to extract
533
- expect(br.read_4b).to eq(0) # number of this disk
534
- expect(br.read_4b).to eq(0) # another number related to disk
535
- expect(br.read_8b).to eq(3) # total number of entries in the central directory on this disk
536
- expect(br.read_8b).to eq(3) # total number of entries in the central directory
537
- expect(br.read_8b).to eq(181) # size of central directory
538
- expect(br.read_8b).to eq(6442453602) # central directory offset from start of disk
539
-
540
- expect(br.read_4b).to eq(0x07064b50) # Zip64 EOCD locator signature
541
- expect(br.read_4b).to eq(0) # Disk number with the start of central directory
542
- expect(br.read_8b).to eq(6442453783) # relative offset of the zip64 end of central directory record
543
- expect(br.read_4b).to eq(1) # total number of disks
544
- end
545
- end
546
- end
@@ -1,22 +0,0 @@
1
- require_relative '../spec_helper'
2
-
3
- describe ZipTricks::StoredSizeEstimator do
4
- it 'accurately predicts the output zip size' do
5
- # Generate a couple of random files
6
- raw_file_1 = SecureRandom.random_bytes(1024 * 20)
7
- raw_file_2 = SecureRandom.random_bytes(1024 * 128)
8
- raw_file_3 = SecureRandom.random_bytes(1258695)
9
-
10
- predicted_size = described_class.perform_fake_archiving do | estimator |
11
- r = estimator.add_stored_entry("first-file.bin", raw_file_1.size)
12
- expect(r).to eq(estimator), "add_stored_entry should return self"
13
-
14
- estimator.add_stored_entry("second-file.bin", raw_file_2.size)
15
-
16
- r = estimator.add_compressed_entry("second-flie.bin", raw_file_2.size, raw_file_3.size)
17
- expect(r).to eq(estimator), "add_compressed_entry should return self"
18
- end
19
-
20
- expect(predicted_size).to eq(1410585)
21
- end
22
- end