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,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"])
|