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.
- checksums.yaml +4 -4
- data/Gemfile +3 -3
- data/IMPLEMENTATION_DETAILS.md +2 -10
- data/README.md +62 -59
- data/examples/archive_size_estimate.rb +4 -4
- data/examples/rack_application.rb +3 -5
- data/lib/zip_tricks/block_deflate.rb +21 -0
- data/lib/zip_tricks/file_reader.rb +491 -0
- data/lib/zip_tricks/null_writer.rb +7 -2
- data/lib/zip_tricks/rack_body.rb +3 -3
- data/lib/zip_tricks/remote_io.rb +30 -20
- data/lib/zip_tricks/remote_uncap.rb +10 -10
- data/lib/zip_tricks/size_estimator.rb +64 -0
- data/lib/zip_tricks/stream_crc32.rb +2 -2
- data/lib/zip_tricks/streamer/deflated_writer.rb +26 -0
- data/lib/zip_tricks/streamer/entry.rb +21 -0
- data/lib/zip_tricks/streamer/stored_writer.rb +25 -0
- data/lib/zip_tricks/streamer/writable.rb +20 -0
- data/lib/zip_tricks/streamer.rb +172 -66
- data/lib/zip_tricks/zip_writer.rb +346 -0
- data/lib/zip_tricks.rb +1 -4
- data/spec/spec_helper.rb +1 -38
- data/spec/zip_tricks/file_reader_spec.rb +47 -0
- data/spec/zip_tricks/rack_body_spec.rb +2 -2
- data/spec/zip_tricks/remote_io_spec.rb +8 -20
- data/spec/zip_tricks/remote_uncap_spec.rb +4 -4
- data/spec/zip_tricks/size_estimator_spec.rb +31 -0
- data/spec/zip_tricks/streamer_spec.rb +59 -36
- data/spec/zip_tricks/zip_writer_spec.rb +408 -0
- data/zip_tricks.gemspec +20 -14
- metadata +33 -16
- data/lib/zip_tricks/manifest.rb +0 -85
- data/lib/zip_tricks/microzip.rb +0 -339
- data/lib/zip_tricks/stored_size_estimator.rb +0 -44
- data/spec/zip_tricks/manifest_spec.rb +0 -60
- data/spec/zip_tricks/microzip_interop_spec.rb +0 -48
- data/spec/zip_tricks/microzip_spec.rb +0 -546
- 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
|