zip_tricks 2.8.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,6 +1,4 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
|
-
require 'fileutils'
|
3
|
-
require 'shellwords'
|
4
2
|
|
5
3
|
describe ZipTricks::Streamer do
|
6
4
|
let(:test_text_file_path) {
|
@@ -30,7 +28,7 @@ describe ZipTricks::Streamer do
|
|
30
28
|
it 'returns the position in the IO at every call' do
|
31
29
|
io = StringIO.new
|
32
30
|
zip = described_class.new(io)
|
33
|
-
pos = zip.add_compressed_entry('file.jpg', 182919,
|
31
|
+
pos = zip.add_compressed_entry(filename: 'file.jpg', uncompressed_size: 182919, compressed_size: 8912, crc32: 8912)
|
34
32
|
expect(pos).to eq(io.tell)
|
35
33
|
expect(pos).to eq(38)
|
36
34
|
|
@@ -38,17 +36,14 @@ describe ZipTricks::Streamer do
|
|
38
36
|
expect(retval).to eq(zip)
|
39
37
|
expect(io.tell).to eq(8950)
|
40
38
|
|
41
|
-
pos = zip.add_stored_entry('filf.jpg', 8921, 182919)
|
39
|
+
pos = zip.add_stored_entry(filename: 'filf.jpg', size: 8921, crc32: 182919)
|
42
40
|
expect(pos).to eq(8988)
|
43
41
|
zip << SecureRandom.random_bytes(8921)
|
44
42
|
expect(io.tell).to eq(17909)
|
45
43
|
|
46
|
-
pos = zip.
|
44
|
+
pos = zip.close
|
47
45
|
expect(pos).to eq(io.tell)
|
48
|
-
expect(pos).to eq(
|
49
|
-
|
50
|
-
pos_after_close = zip.close
|
51
|
-
expect(pos_after_close).to eq(pos)
|
46
|
+
expect(pos).to eq(18068)
|
52
47
|
end
|
53
48
|
|
54
49
|
it 'can write and then read the block-deflated files' do
|
@@ -73,7 +68,8 @@ describe ZipTricks::Streamer do
|
|
73
68
|
zip_file.binmode
|
74
69
|
|
75
70
|
described_class.open(zip_file) do |zip|
|
76
|
-
zip.add_compressed_entry("compressed-file.bin", f.size,
|
71
|
+
zip.add_compressed_entry(filename: "compressed-file.bin", uncompressed_size: f.size,
|
72
|
+
crc32: crc, compressed_size: compressed_blockwise.size)
|
77
73
|
zip << compressed_blockwise.read
|
78
74
|
end
|
79
75
|
zip_file.flush
|
@@ -112,11 +108,12 @@ describe ZipTricks::Streamer do
|
|
112
108
|
end
|
113
109
|
|
114
110
|
# Add this file compressed...
|
115
|
-
zip.add_compressed_entry('war-and-peace.txt', source_f.size,
|
111
|
+
zip.add_compressed_entry(filename: 'war-and-peace.txt', uncompressed_size: source_f.size,
|
112
|
+
crc32: crc32, compressed_size: compressed_buffer.size)
|
116
113
|
zip << compressed_buffer.string
|
117
114
|
|
118
115
|
# ...and stored.
|
119
|
-
zip.add_stored_entry('war-and-peace-raw.txt', source_f.size, crc32)
|
116
|
+
zip.add_stored_entry(filename: 'war-and-peace-raw.txt', size: source_f.size, crc32: crc32)
|
120
117
|
zip << source_f.read
|
121
118
|
|
122
119
|
zip.close
|
@@ -154,9 +151,9 @@ describe ZipTricks::Streamer do
|
|
154
151
|
|
155
152
|
# Perform the zipping
|
156
153
|
zip = described_class.new(output_io)
|
157
|
-
zip.add_stored_entry("first-file.bin", raw_file_1.size, Zlib.crc32(raw_file_1))
|
154
|
+
zip.add_stored_entry(filename: "first-file.bin", size: raw_file_1.size, crc32: Zlib.crc32(raw_file_1))
|
158
155
|
zip << raw_file_1
|
159
|
-
zip.add_stored_entry("second-file.bin", raw_file_2.size, Zlib.crc32(raw_file_2))
|
156
|
+
zip.add_stored_entry(filename: "second-file.bin", size: raw_file_2.size, crc32: Zlib.crc32(raw_file_2))
|
160
157
|
zip << raw_file_2
|
161
158
|
zip.close
|
162
159
|
|
@@ -193,9 +190,9 @@ describe ZipTricks::Streamer do
|
|
193
190
|
|
194
191
|
# Perform the zipping
|
195
192
|
zip = described_class.new(zip_buf)
|
196
|
-
zip.add_stored_entry("first-file.bin", raw_file_1.size, Zlib.crc32(raw_file_1))
|
193
|
+
zip.add_stored_entry(filename: "first-file.bin", size: raw_file_1.size, crc32: Zlib.crc32(raw_file_1))
|
197
194
|
zip << raw_file_1
|
198
|
-
zip.add_stored_entry("второй-файл.bin", raw_file_2.size, Zlib.crc32(raw_file_2))
|
195
|
+
zip.add_stored_entry(filename: "второй-файл.bin", size: raw_file_2.size, crc32: Zlib.crc32(raw_file_2))
|
199
196
|
IO.copy_stream(StringIO.new(raw_file_2), zip)
|
200
197
|
zip.close
|
201
198
|
|
@@ -215,28 +212,54 @@ describe ZipTricks::Streamer do
|
|
215
212
|
expect(second_entry.name).to eq("второй-файл.bin".force_encoding(Encoding::BINARY))
|
216
213
|
end
|
217
214
|
end
|
218
|
-
|
219
|
-
it '
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
215
|
+
|
216
|
+
it 'creates an archive with data descriptors that can be opened by Rubyzip, with a small number of very tiny text files' do
|
217
|
+
tf = ManagedTempfile.new('zip')
|
218
|
+
z = described_class.open(tf) do |zip|
|
219
|
+
zip.write_stored_file('deflated.txt') do |sink|
|
220
|
+
sink << File.read(__dir__ + '/war-and-peace.txt')
|
224
221
|
end
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
222
|
+
zip.write_deflated_file('stored.txt') do |sink|
|
223
|
+
sink << File.read(__dir__ + '/war-and-peace.txt')
|
224
|
+
end
|
225
|
+
end
|
226
|
+
tf.flush
|
227
|
+
|
228
|
+
pending 'https://github.com/rubyzip/rubyzip/issues/295'
|
229
|
+
|
230
|
+
Zip::File.foreach(tf.path) do |entry|
|
231
|
+
# Make sure it is tagged as UNIX
|
232
|
+
expect(entry.fstype).to eq(3)
|
233
|
+
|
234
|
+
# The CRC
|
235
|
+
expect(entry.crc).to eq(Zlib.crc32(File.read(__dir__ + '/war-and-peace.txt')))
|
236
|
+
|
237
|
+
# Check the name
|
238
|
+
expect(entry.name).to match(/\.txt$/)
|
239
|
+
|
240
|
+
# Check the right external attributes (non-executable on UNIX)
|
241
|
+
expect(entry.external_file_attributes).to eq(2175008768)
|
242
|
+
|
243
|
+
# Check the file contents
|
244
|
+
readback = entry.get_input_stream.read
|
245
|
+
readback.force_encoding(Encoding::BINARY)
|
246
|
+
expect(readback[0..10]).to eq(File.read(__dir__ + '/war-and-peace.txt')[0..10])
|
247
|
+
end
|
248
|
+
|
249
|
+
inspect_zip_with_external_tool(tf.path)
|
229
250
|
end
|
230
251
|
|
231
|
-
it '
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
252
|
+
it 'can create a valid ZIP archive without any files' do
|
253
|
+
tf = ManagedTempfile.new('zip')
|
254
|
+
|
255
|
+
described_class.open(tf) do |zip|
|
256
|
+
end
|
257
|
+
|
258
|
+
tf.flush
|
259
|
+
tf.rewind
|
260
|
+
|
261
|
+
expect { |b|
|
262
|
+
Zip::File.foreach(tf.path, &b)
|
263
|
+
}.not_to yield_control
|
241
264
|
end
|
242
265
|
end
|
@@ -0,0 +1,408 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../testing/support'
|
3
|
+
|
4
|
+
describe ZipTricks::ZipWriter do
|
5
|
+
class ByteReader < Struct.new(:io)
|
6
|
+
def initialize(io)
|
7
|
+
super(io).tap { io.rewind }
|
8
|
+
end
|
9
|
+
|
10
|
+
def read_2b
|
11
|
+
read_n(2).unpack('v').first
|
12
|
+
end
|
13
|
+
|
14
|
+
def read_2c
|
15
|
+
read_n(2).unpack('CC').first
|
16
|
+
end
|
17
|
+
|
18
|
+
def read_4b
|
19
|
+
read_n(4).unpack('V').first
|
20
|
+
end
|
21
|
+
|
22
|
+
def read_8b
|
23
|
+
read_n(8).unpack('Q<').first
|
24
|
+
end
|
25
|
+
|
26
|
+
def read_n(n)
|
27
|
+
io.read(n).tap {|r|
|
28
|
+
raise "Expected to read #{n} bytes, but read() returned nil" if r.nil?
|
29
|
+
raise "Expected to read #{n} bytes, but read #{r.bytesize} instead" if r.bytesize != n
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# For conveniently going to a specific signature
|
34
|
+
def seek_to_start_of_signature(signature)
|
35
|
+
io.rewind
|
36
|
+
signature_encoded = [signature].pack('V')
|
37
|
+
idx = io.read.index(signature_encoded)
|
38
|
+
raise "Could not find the signature #{signature} in the buffer" unless idx
|
39
|
+
io.seek(idx, IO::SEEK_SET)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#write_local_file_header' do
|
44
|
+
it 'writes the local file header for an entry that does not require Zip64' do
|
45
|
+
buf = StringIO.new
|
46
|
+
mtime = Time.utc(2016, 7, 17, 13, 48)
|
47
|
+
|
48
|
+
subject = ZipTricks::ZipWriter.new
|
49
|
+
subject.write_local_file_header(io: buf, gp_flags: 12, crc32: 456, compressed_size: 768, uncompressed_size: 901, mtime: mtime, filename: 'foo.bin', storage_mode: 8)
|
50
|
+
|
51
|
+
br = ByteReader.new(buf)
|
52
|
+
expect(br.read_4b).to eq(0x04034b50) # Signature
|
53
|
+
expect(br.read_2b).to eq(20) # Version needed to extract
|
54
|
+
expect(br.read_2b).to eq(12) # gp flags
|
55
|
+
expect(br.read_2b).to eq(8) # storage mode
|
56
|
+
expect(br.read_2b).to eq(28160) # DOS time
|
57
|
+
expect(br.read_2b).to eq(18673) # DOS date
|
58
|
+
expect(br.read_4b).to eq(456) # CRC32
|
59
|
+
expect(br.read_4b).to eq(768) # compressed size
|
60
|
+
expect(br.read_4b).to eq(901) # uncompressed size
|
61
|
+
expect(br.read_2b).to eq(7) # filename size
|
62
|
+
expect(br.read_2b).to eq(0) # extra fields size
|
63
|
+
expect(br.read_n(7)).to eq('foo.bin') # extra fields size
|
64
|
+
expect(buf).to be_eof
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'writes the local file header for an entry that does require Zip64 based on uncompressed size (with the Zip64 extra)' do
|
68
|
+
buf = StringIO.new
|
69
|
+
mtime = Time.utc(2016, 7, 17, 13, 48)
|
70
|
+
|
71
|
+
subject = ZipTricks::ZipWriter.new
|
72
|
+
subject.write_local_file_header(io: buf, gp_flags: 12, crc32: 456, compressed_size: 768, uncompressed_size: 0xFFFFFFFF+1, mtime: mtime, filename: 'foo.bin', storage_mode: 8)
|
73
|
+
|
74
|
+
br = ByteReader.new(buf)
|
75
|
+
expect(br.read_4b).to eq(0x04034b50) # Signature
|
76
|
+
expect(br.read_2b).to eq(45) # Version needed to extract
|
77
|
+
expect(br.read_2b).to eq(12) # gp flags
|
78
|
+
expect(br.read_2b).to eq(8) # storage mode
|
79
|
+
expect(br.read_2b).to eq(28160) # DOS time
|
80
|
+
expect(br.read_2b).to eq(18673) # DOS date
|
81
|
+
expect(br.read_4b).to eq(456) # CRC32
|
82
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # compressed size
|
83
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # uncompressed size
|
84
|
+
expect(br.read_2b).to eq(7) # filename size
|
85
|
+
expect(br.read_2b).to eq(20) # extra fields size
|
86
|
+
expect(br.read_n(7)).to eq('foo.bin') # extra fields size
|
87
|
+
|
88
|
+
expect(buf).not_to be_eof
|
89
|
+
|
90
|
+
expect(br.read_2b).to eq(1) # Zip64 extra tag
|
91
|
+
expect(br.read_2b).to eq(16) # Size of the Zip64 extra payload
|
92
|
+
expect(br.read_8b).to eq(0xFFFFFFFF+1) # uncompressed size
|
93
|
+
expect(br.read_8b).to eq(768) # compressed size
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'writes the local file header for an entry that does require Zip64 based on compressed size (with the Zip64 extra)' do
|
97
|
+
buf = StringIO.new
|
98
|
+
mtime = Time.utc(2016, 7, 17, 13, 48)
|
99
|
+
|
100
|
+
subject = ZipTricks::ZipWriter.new
|
101
|
+
subject.write_local_file_header(io: buf, gp_flags: 12, crc32: 456, compressed_size: 0xFFFFFFFF+1, uncompressed_size: 768, mtime: mtime, filename: 'foo.bin', storage_mode: 8)
|
102
|
+
|
103
|
+
br = ByteReader.new(buf)
|
104
|
+
expect(br.read_4b).to eq(0x04034b50) # Signature
|
105
|
+
expect(br.read_2b).to eq(45) # Version needed to extract
|
106
|
+
expect(br.read_2b).to eq(12) # gp flags
|
107
|
+
expect(br.read_2b).to eq(8) # storage mode
|
108
|
+
expect(br.read_2b).to eq(28160) # DOS time
|
109
|
+
expect(br.read_2b).to eq(18673) # DOS date
|
110
|
+
expect(br.read_4b).to eq(456) # CRC32
|
111
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # compressed size
|
112
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # uncompressed size
|
113
|
+
expect(br.read_2b).to eq(7) # filename size
|
114
|
+
expect(br.read_2b).to eq(20) # extra fields size
|
115
|
+
expect(br.read_n(7)).to eq('foo.bin') # extra fields size
|
116
|
+
|
117
|
+
expect(buf).not_to be_eof
|
118
|
+
|
119
|
+
expect(br.read_2b).to eq(1) # Zip64 extra tag
|
120
|
+
expect(br.read_2b).to eq(16) # Size of the Zip64 extra payload
|
121
|
+
expect(br.read_8b).to eq(768) # uncompressed size
|
122
|
+
expect(br.read_8b).to eq(0xFFFFFFFF+1) # compressed size
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#write_data_descriptor' do
|
127
|
+
it 'writes 4-byte sizes into the data descriptor for standard file sizes' do
|
128
|
+
buf = StringIO.new
|
129
|
+
|
130
|
+
subject.write_data_descriptor(io: buf, crc32: 123, compressed_size: 89821, uncompressed_size: 990912)
|
131
|
+
|
132
|
+
br = ByteReader.new(buf)
|
133
|
+
expect(br.read_4b).to eq(0x08074b50) # Signature
|
134
|
+
expect(br.read_4b).to eq(123) # CRC32
|
135
|
+
expect(br.read_4b).to eq(89821) # compressed size
|
136
|
+
expect(br.read_4b).to eq(990912) # uncompressed size
|
137
|
+
expect(buf).to be_eof
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'writes 8-byte sizes into the data descriptor for Zip64 compressed file size' do
|
141
|
+
buf = StringIO.new
|
142
|
+
|
143
|
+
subject.write_data_descriptor(io: buf, crc32: 123, compressed_size: 0xFFFFFFFF + 1, uncompressed_size: 990912)
|
144
|
+
|
145
|
+
br = ByteReader.new(buf)
|
146
|
+
expect(br.read_4b).to eq(0x08074b50) # Signature
|
147
|
+
expect(br.read_4b).to eq(123) # CRC32
|
148
|
+
expect(br.read_8b).to eq(0xFFFFFFFF + 1) # compressed size
|
149
|
+
expect(br.read_8b).to eq(990912) # uncompressed size
|
150
|
+
expect(buf).to be_eof
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'writes 8-byte sizes into the data descriptor for Zip64 uncompressed file size' do
|
154
|
+
buf = StringIO.new
|
155
|
+
|
156
|
+
subject.write_data_descriptor(io: buf, crc32: 123, compressed_size: 123, uncompressed_size: 0xFFFFFFFF + 1)
|
157
|
+
|
158
|
+
br = ByteReader.new(buf)
|
159
|
+
expect(br.read_4b).to eq(0x08074b50) # Signature
|
160
|
+
expect(br.read_4b).to eq(123) # CRC32
|
161
|
+
expect(br.read_8b).to eq(123) # compressed size
|
162
|
+
expect(br.read_8b).to eq(0xFFFFFFFF + 1) # uncompressed size
|
163
|
+
expect(buf).to be_eof
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '#write_central_directory_file_header' do
|
168
|
+
it 'writes the file header for a small-ish entry' do
|
169
|
+
buf = StringIO.new
|
170
|
+
|
171
|
+
subject.write_central_directory_file_header(io: buf, local_file_header_location: 898921,
|
172
|
+
gp_flags: 555, storage_mode: 23,
|
173
|
+
compressed_size: 901, uncompressed_size: 909102,
|
174
|
+
mtime: Time.utc(2016, 2, 2, 14, 00), crc32: 89765,
|
175
|
+
filename: 'a-file.txt', external_attrs: 123)
|
176
|
+
|
177
|
+
br = ByteReader.new(buf)
|
178
|
+
expect(br.read_4b).to eq(0x02014b50) # Central directory entry sig
|
179
|
+
expect(br.read_2b).to eq(820) # version made by
|
180
|
+
expect(br.read_2b).to eq(20) # version need to extract
|
181
|
+
expect(br.read_2b).to eq(555) # general purpose bit flag (explicitly set to bogus value to ensure we pass it through)
|
182
|
+
expect(br.read_2b).to eq(23) # compression method (explicitly set to bogus value)
|
183
|
+
expect(br.read_2b).to eq(28672) # last mod file time
|
184
|
+
expect(br.read_2b).to eq(18498) # last mod file date
|
185
|
+
expect(br.read_4b).to eq(89765) # crc32
|
186
|
+
expect(br.read_4b).to eq(901) # compressed size
|
187
|
+
expect(br.read_4b).to eq(909102) # uncompressed size
|
188
|
+
expect(br.read_2b).to eq(10) # filename length
|
189
|
+
expect(br.read_2b).to eq(0) # extra field length
|
190
|
+
expect(br.read_2b).to eq(0) # file comment
|
191
|
+
expect(br.read_2b).to eq(0) # disk number, must be blanked to the maximum value because of The Unarchiver bug
|
192
|
+
expect(br.read_2b).to eq(0) # internal file attributes
|
193
|
+
expect(br.read_4b).to eq(2175008768) # external file attributes
|
194
|
+
expect(br.read_4b).to eq(898921) # relative offset of local header
|
195
|
+
expect(br.read_n(10)).to eq('a-file.txt') # the filename
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'writes the file header for an entry that requires Zip64 extra because of the uncompressed size' do
|
199
|
+
buf = StringIO.new
|
200
|
+
|
201
|
+
subject.write_central_directory_file_header(io: buf, local_file_header_location: 898921,
|
202
|
+
gp_flags: 555, storage_mode: 23,
|
203
|
+
compressed_size: 901, uncompressed_size: 0xFFFFFFFFF + 3,
|
204
|
+
mtime: Time.utc(2016, 2, 2, 14, 00), crc32: 89765,
|
205
|
+
filename: 'a-file.txt', external_attrs: 123)
|
206
|
+
|
207
|
+
br = ByteReader.new(buf)
|
208
|
+
expect(br.read_4b).to eq(0x02014b50) # Central directory entry sig
|
209
|
+
expect(br.read_2b).to eq(820) # version made by
|
210
|
+
expect(br.read_2b).to eq(45) # version need to extract
|
211
|
+
expect(br.read_2b).to eq(555) # general purpose bit flag (explicitly set to bogus value to ensure we pass it through)
|
212
|
+
expect(br.read_2b).to eq(23) # compression method (explicitly set to bogus value)
|
213
|
+
expect(br.read_2b).to eq(28672) # last mod file time
|
214
|
+
expect(br.read_2b).to eq(18498) # last mod file date
|
215
|
+
expect(br.read_4b).to eq(89765) # crc32
|
216
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # compressed size
|
217
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # uncompressed size
|
218
|
+
expect(br.read_2b).to eq(10) # filename length
|
219
|
+
expect(br.read_2b).to eq(32) # extra field length
|
220
|
+
expect(br.read_2b).to eq(0) # file comment
|
221
|
+
expect(br.read_2b).to eq(0xFFFF) # disk number, must be blanked to the maximum value because of The Unarchiver bug
|
222
|
+
expect(br.read_2b).to eq(0) # internal file attributes
|
223
|
+
expect(br.read_4b).to eq(2175008768) # external file attributes
|
224
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # relative offset of local header
|
225
|
+
expect(br.read_n(10)).to eq('a-file.txt') # the filename
|
226
|
+
|
227
|
+
expect(buf).not_to be_eof
|
228
|
+
expect(br.read_2b).to eq(1) # Zip64 extra tag
|
229
|
+
expect(br.read_2b).to eq(28) # Size of the Zip64 extra payload
|
230
|
+
expect(br.read_8b).to eq(0xFFFFFFFFF + 3) # uncompressed size
|
231
|
+
expect(br.read_8b).to eq(901) # compressed size
|
232
|
+
expect(br.read_8b).to eq(898921) # local file header location
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'writes the file header for an entry that requires Zip64 extra because of the compressed size' do
|
236
|
+
buf = StringIO.new
|
237
|
+
|
238
|
+
subject.write_central_directory_file_header(io: buf, local_file_header_location: 898921,
|
239
|
+
gp_flags: 555, storage_mode: 23,
|
240
|
+
compressed_size: 0xFFFFFFFFF + 3, uncompressed_size: 901, # the worst compression scheme in the universe
|
241
|
+
mtime: Time.utc(2016, 2, 2, 14, 00), crc32: 89765,
|
242
|
+
filename: 'a-file.txt', external_attrs: 123)
|
243
|
+
|
244
|
+
br = ByteReader.new(buf)
|
245
|
+
expect(br.read_4b).to eq(0x02014b50) # Central directory entry sig
|
246
|
+
expect(br.read_2b).to eq(820) # version made by
|
247
|
+
expect(br.read_2b).to eq(45) # version need to extract
|
248
|
+
expect(br.read_2b).to eq(555) # general purpose bit flag (explicitly set to bogus value to ensure we pass it through)
|
249
|
+
expect(br.read_2b).to eq(23) # compression method (explicitly set to bogus value)
|
250
|
+
expect(br.read_2b).to eq(28672) # last mod file time
|
251
|
+
expect(br.read_2b).to eq(18498) # last mod file date
|
252
|
+
expect(br.read_4b).to eq(89765) # crc32
|
253
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # compressed size
|
254
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # uncompressed size
|
255
|
+
expect(br.read_2b).to eq(10) # filename length
|
256
|
+
expect(br.read_2b).to eq(32) # extra field length
|
257
|
+
expect(br.read_2b).to eq(0) # file comment
|
258
|
+
expect(br.read_2b).to eq(0xFFFF) # disk number, must be blanked to the maximum value because of The Unarchiver bug
|
259
|
+
expect(br.read_2b).to eq(0) # internal file attributes
|
260
|
+
expect(br.read_4b).to eq(2175008768) # external file attributes
|
261
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # relative offset of local header
|
262
|
+
expect(br.read_n(10)).to eq('a-file.txt') # the filename
|
263
|
+
|
264
|
+
expect(buf).not_to be_eof
|
265
|
+
expect(br.read_2b).to eq(1) # Zip64 extra tag
|
266
|
+
expect(br.read_2b).to eq(28) # Size of the Zip64 extra payload
|
267
|
+
expect(br.read_8b).to eq(901) # uncompressed size
|
268
|
+
expect(br.read_8b).to eq(0xFFFFFFFFF + 3) # compressed size
|
269
|
+
expect(br.read_8b).to eq(898921) # local file header location
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'writes the file header for an entry that requires Zip64 extra because of the local file header offset being beyound 4GB' do
|
273
|
+
buf = StringIO.new
|
274
|
+
|
275
|
+
subject.write_central_directory_file_header(io: buf, local_file_header_location: 0xFFFFFFFFF + 1,
|
276
|
+
gp_flags: 555, storage_mode: 23,
|
277
|
+
compressed_size: 8981, uncompressed_size: 819891, # the worst compression scheme in the universe
|
278
|
+
mtime: Time.utc(2016, 2, 2, 14, 00), crc32: 89765,
|
279
|
+
filename: 'a-file.txt', external_attrs: 123)
|
280
|
+
|
281
|
+
br = ByteReader.new(buf)
|
282
|
+
expect(br.read_4b).to eq(0x02014b50) # Central directory entry sig
|
283
|
+
expect(br.read_2b).to eq(820) # version made by
|
284
|
+
expect(br.read_2b).to eq(45) # version need to extract
|
285
|
+
expect(br.read_2b).to eq(555) # general purpose bit flag (explicitly set to bogus value to ensure we pass it through)
|
286
|
+
expect(br.read_2b).to eq(23) # compression method (explicitly set to bogus value)
|
287
|
+
expect(br.read_2b).to eq(28672) # last mod file time
|
288
|
+
expect(br.read_2b).to eq(18498) # last mod file date
|
289
|
+
expect(br.read_4b).to eq(89765) # crc32
|
290
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # compressed size
|
291
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # uncompressed size
|
292
|
+
expect(br.read_2b).to eq(10) # filename length
|
293
|
+
expect(br.read_2b).to eq(32) # extra field length
|
294
|
+
expect(br.read_2b).to eq(0) # file comment
|
295
|
+
expect(br.read_2b).to eq(0xFFFF) # disk number, must be blanked to the maximum value because of The Unarchiver bug
|
296
|
+
expect(br.read_2b).to eq(0) # internal file attributes
|
297
|
+
expect(br.read_4b).to eq(2175008768) # external file attributes
|
298
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # relative offset of local header
|
299
|
+
expect(br.read_n(10)).to eq('a-file.txt') # the filename
|
300
|
+
|
301
|
+
expect(buf).not_to be_eof
|
302
|
+
expect(br.read_2b).to eq(1) # Zip64 extra tag
|
303
|
+
expect(br.read_2b).to eq(28) # Size of the Zip64 extra payload
|
304
|
+
expect(br.read_8b).to eq(819891) # uncompressed size
|
305
|
+
expect(br.read_8b).to eq(8981) # compressed size
|
306
|
+
expect(br.read_8b).to eq(0xFFFFFFFFF + 1) # local file header location
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
describe '#write_end_of_central_directory' do
|
311
|
+
it 'writes out the EOCD with all markers for a small ZIP file with just a few entries' do
|
312
|
+
buf = StringIO.new
|
313
|
+
|
314
|
+
num_files = rand(8..190)
|
315
|
+
subject.write_end_of_central_directory(io: buf, start_of_central_directory_location: 9091211,
|
316
|
+
central_directory_size: 9091, num_files_in_archive: num_files)
|
317
|
+
|
318
|
+
br = ByteReader.new(buf)
|
319
|
+
expect(br.read_4b).to eq(0x06054b50) # EOCD signature
|
320
|
+
expect(br.read_2b).to eq(0) # number of this disk
|
321
|
+
expect(br.read_2b).to eq(0) # number of the disk with the EOCD record
|
322
|
+
expect(br.read_2b).to eq(num_files) # number of files on this disk
|
323
|
+
expect(br.read_2b).to eq(num_files) # number of files in central directory total (for all disks)
|
324
|
+
expect(br.read_4b).to eq(9091) # size of the central directory (cdir records for all files)
|
325
|
+
expect(br.read_4b).to eq(9091211) # start of central directory offset from the beginning of file/disk
|
326
|
+
|
327
|
+
comment_length = br.read_2b
|
328
|
+
expect(comment_length).not_to be_zero
|
329
|
+
|
330
|
+
expect(br.read_n(comment_length)).to match(/ZipTricks/)
|
331
|
+
end
|
332
|
+
|
333
|
+
it 'writes out the Zip64 EOCD as well if the central directory is located beyound 4GB in the archive' do
|
334
|
+
buf = StringIO.new
|
335
|
+
|
336
|
+
num_files = rand(8..190)
|
337
|
+
subject.write_end_of_central_directory(io: buf, start_of_central_directory_location: 0xFFFFFFFF + 3,
|
338
|
+
central_directory_size: 9091, num_files_in_archive: num_files)
|
339
|
+
|
340
|
+
br = ByteReader.new(buf)
|
341
|
+
|
342
|
+
expect(br.read_4b).to eq(0x06064b50) # Zip64 EOCD signature
|
343
|
+
expect(br.read_8b).to eq(44) # Zip64 EOCD record size
|
344
|
+
expect(br.read_2b).to eq(820) # Version made by
|
345
|
+
expect(br.read_2b).to eq(45) # Version needed to extract
|
346
|
+
expect(br.read_4b).to eq(0) # Number of this disk
|
347
|
+
expect(br.read_4b).to eq(0) # Number of the disk with the Zip64 EOCD record
|
348
|
+
expect(br.read_8b).to eq(num_files) # Number of entries in the central directory of this disk
|
349
|
+
expect(br.read_8b).to eq(num_files) # Number of entries in the central directories of all disks
|
350
|
+
expect(br.read_8b).to eq(9091) # Central directory size
|
351
|
+
expect(br.read_8b).to eq(0xFFFFFFFF + 3) # Start of central directory location
|
352
|
+
|
353
|
+
expect(br.read_4b).to eq(0x07064b50) # Zip64 EOCD locator signature
|
354
|
+
expect(br.read_4b).to eq(0) # Number of the disk with the EOCD locator signature
|
355
|
+
expect(br.read_8b).to eq((0xFFFFFFFF + 3) + 9091) # Where the Zip64 EOCD record starts
|
356
|
+
expect(br.read_4b).to eq(1) # Total number of disks
|
357
|
+
|
358
|
+
# Then the usual EOCD record
|
359
|
+
expect(br.read_4b).to eq(0x06054b50) # EOCD signature
|
360
|
+
expect(br.read_2b).to eq(0) # number of this disk
|
361
|
+
expect(br.read_2b).to eq(0) # number of the disk with the EOCD record
|
362
|
+
expect(br.read_2b).to eq(0xFFFF) # number of files on this disk
|
363
|
+
expect(br.read_2b).to eq(0xFFFF) # number of files in central directory total (for all disks)
|
364
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # size of the central directory (cdir records for all files)
|
365
|
+
expect(br.read_4b).to eq(0xFFFFFFFF) # start of central directory offset from the beginning of file/disk
|
366
|
+
|
367
|
+
comment_length = br.read_2b
|
368
|
+
expect(comment_length).not_to be_zero
|
369
|
+
expect(br.read_n(comment_length)).to match(/ZipTricks/)
|
370
|
+
end
|
371
|
+
|
372
|
+
it 'writes out the Zip64 EOCD if the archive has more than 0xFFFF files' do
|
373
|
+
buf = StringIO.new
|
374
|
+
|
375
|
+
subject.write_end_of_central_directory(io: buf, start_of_central_directory_location: 123,
|
376
|
+
central_directory_size: 9091, num_files_in_archive: 0xFFFF + 1)
|
377
|
+
|
378
|
+
br = ByteReader.new(buf)
|
379
|
+
|
380
|
+
expect(br.read_4b).to eq(0x06064b50) # Zip64 EOCD signature
|
381
|
+
br.read_8b
|
382
|
+
br.read_2b
|
383
|
+
br.read_2b
|
384
|
+
br.read_4b
|
385
|
+
br.read_4b
|
386
|
+
expect(br.read_8b).to eq(0xFFFF + 1) # Number of entries in the central directory of this disk
|
387
|
+
expect(br.read_8b).to eq(0xFFFF + 1) # Number of entries in the central directories of all disks
|
388
|
+
end
|
389
|
+
|
390
|
+
it 'writes out the Zip64 EOCD if the central directory size exceeds 0xFFFFFFFF' do
|
391
|
+
buf = StringIO.new
|
392
|
+
|
393
|
+
subject.write_end_of_central_directory(io: buf, start_of_central_directory_location: 123,
|
394
|
+
central_directory_size: 0xFFFFFFFF + 2, num_files_in_archive: 5)
|
395
|
+
|
396
|
+
br = ByteReader.new(buf)
|
397
|
+
|
398
|
+
expect(br.read_4b).to eq(0x06064b50) # Zip64 EOCD signature
|
399
|
+
br.read_8b
|
400
|
+
br.read_2b
|
401
|
+
br.read_2b
|
402
|
+
br.read_4b
|
403
|
+
br.read_4b
|
404
|
+
expect(br.read_8b).to eq(5) # Number of entries in the central directory of this disk
|
405
|
+
expect(br.read_8b).to eq(5) # Number of entries in the central directories of all disks
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
data/zip_tricks.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: zip_tricks
|
5
|
+
# stub: zip_tricks 3.0.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "zip_tricks"
|
9
|
-
s.version = "
|
9
|
+
s.version = "3.0.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Julik Tarkhanov"]
|
14
|
-
s.date = "2016-
|
14
|
+
s.date = "2016-08-03"
|
15
15
|
s.description = "Makes rubyzip stream, for real"
|
16
16
|
s.email = "me@julik.nl"
|
17
17
|
s.extra_rdoc_files = [
|
@@ -35,30 +35,33 @@ Gem::Specification.new do |s|
|
|
35
35
|
"lib/zip_tricks.rb",
|
36
36
|
"lib/zip_tricks/block_deflate.rb",
|
37
37
|
"lib/zip_tricks/block_write.rb",
|
38
|
-
"lib/zip_tricks/
|
39
|
-
"lib/zip_tricks/microzip.rb",
|
38
|
+
"lib/zip_tricks/file_reader.rb",
|
40
39
|
"lib/zip_tricks/null_writer.rb",
|
41
40
|
"lib/zip_tricks/rack_body.rb",
|
42
41
|
"lib/zip_tricks/remote_io.rb",
|
43
42
|
"lib/zip_tricks/remote_uncap.rb",
|
44
|
-
"lib/zip_tricks/
|
43
|
+
"lib/zip_tricks/size_estimator.rb",
|
45
44
|
"lib/zip_tricks/stream_crc32.rb",
|
46
45
|
"lib/zip_tricks/streamer.rb",
|
46
|
+
"lib/zip_tricks/streamer/deflated_writer.rb",
|
47
|
+
"lib/zip_tricks/streamer/entry.rb",
|
48
|
+
"lib/zip_tricks/streamer/stored_writer.rb",
|
49
|
+
"lib/zip_tricks/streamer/writable.rb",
|
47
50
|
"lib/zip_tricks/write_and_tell.rb",
|
51
|
+
"lib/zip_tricks/zip_writer.rb",
|
48
52
|
"spec/spec_helper.rb",
|
49
53
|
"spec/zip_tricks/block_deflate_spec.rb",
|
50
54
|
"spec/zip_tricks/block_write_spec.rb",
|
51
|
-
"spec/zip_tricks/
|
52
|
-
"spec/zip_tricks/microzip_interop_spec.rb",
|
53
|
-
"spec/zip_tricks/microzip_spec.rb",
|
55
|
+
"spec/zip_tricks/file_reader_spec.rb",
|
54
56
|
"spec/zip_tricks/rack_body_spec.rb",
|
55
57
|
"spec/zip_tricks/remote_io_spec.rb",
|
56
58
|
"spec/zip_tricks/remote_uncap_spec.rb",
|
57
|
-
"spec/zip_tricks/
|
59
|
+
"spec/zip_tricks/size_estimator_spec.rb",
|
58
60
|
"spec/zip_tricks/stream_crc32_spec.rb",
|
59
61
|
"spec/zip_tricks/streamer_spec.rb",
|
60
62
|
"spec/zip_tricks/war-and-peace.txt",
|
61
63
|
"spec/zip_tricks/write_and_tell_spec.rb",
|
64
|
+
"spec/zip_tricks/zip_writer_spec.rb",
|
62
65
|
"zip_tricks.gemspec"
|
63
66
|
]
|
64
67
|
s.homepage = "http://github.com/wetransfer/zip_tricks"
|
@@ -70,33 +73,36 @@ Gem::Specification.new do |s|
|
|
70
73
|
s.specification_version = 4
|
71
74
|
|
72
75
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
73
|
-
s.
|
74
|
-
s.
|
76
|
+
s.add_development_dependency(%q<rubyzip>, [">= 1.1.7", "~> 1.1"])
|
77
|
+
s.add_development_dependency(%q<terminal-table>, [">= 0"])
|
75
78
|
s.add_development_dependency(%q<range_utils>, [">= 0"])
|
76
79
|
s.add_development_dependency(%q<rack>, ["~> 1.6"])
|
77
80
|
s.add_development_dependency(%q<rake>, ["~> 10.4"])
|
78
81
|
s.add_development_dependency(%q<rspec>, ["< 3.3", "~> 3.2.0"])
|
82
|
+
s.add_development_dependency(%q<coderay>, [">= 0"])
|
79
83
|
s.add_development_dependency(%q<yard>, ["~> 0.8"])
|
80
84
|
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
81
85
|
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
82
86
|
else
|
83
87
|
s.add_dependency(%q<rubyzip>, [">= 1.1.7", "~> 1.1"])
|
84
|
-
s.add_dependency(%q<
|
88
|
+
s.add_dependency(%q<terminal-table>, [">= 0"])
|
85
89
|
s.add_dependency(%q<range_utils>, [">= 0"])
|
86
90
|
s.add_dependency(%q<rack>, ["~> 1.6"])
|
87
91
|
s.add_dependency(%q<rake>, ["~> 10.4"])
|
88
92
|
s.add_dependency(%q<rspec>, ["< 3.3", "~> 3.2.0"])
|
93
|
+
s.add_dependency(%q<coderay>, [">= 0"])
|
89
94
|
s.add_dependency(%q<yard>, ["~> 0.8"])
|
90
95
|
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
91
96
|
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
92
97
|
end
|
93
98
|
else
|
94
99
|
s.add_dependency(%q<rubyzip>, [">= 1.1.7", "~> 1.1"])
|
95
|
-
s.add_dependency(%q<
|
100
|
+
s.add_dependency(%q<terminal-table>, [">= 0"])
|
96
101
|
s.add_dependency(%q<range_utils>, [">= 0"])
|
97
102
|
s.add_dependency(%q<rack>, ["~> 1.6"])
|
98
103
|
s.add_dependency(%q<rake>, ["~> 10.4"])
|
99
104
|
s.add_dependency(%q<rspec>, ["< 3.3", "~> 3.2.0"])
|
105
|
+
s.add_dependency(%q<coderay>, [">= 0"])
|
100
106
|
s.add_dependency(%q<yard>, ["~> 0.8"])
|
101
107
|
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
102
108
|
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|