zip_tricks 4.2.3 → 4.2.4

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.
@@ -1,287 +0,0 @@
1
- require 'spec_helper'
2
- describe ZipTricks::FileReader do
3
-
4
- context 'with a file without EOCD' do
5
- it 'raises the MissingEOCD exception and refuses to read' do
6
- f = StringIO.new
7
- 10.times { f << ('A' * 1024 ) }
8
- f.rewind
9
-
10
- expect {
11
- described_class.read_zip_structure(io: f)
12
- }.to raise_error(described_class::MissingEOCD)
13
- end
14
- end
15
-
16
- describe 'read_zip_straight_ahead' do
17
- it 'returns all the entries it can recover' do
18
- zipfile = StringIO.new
19
- war_and_peace = File.read(__dir__ + '/war-and-peace.txt')
20
- ZipTricks::Streamer.open(zipfile) do |zip|
21
- zip.add_stored_entry filename: 'text1.txt', crc32: Zlib.crc32(war_and_peace), size: war_and_peace.bytesize
22
- zip << war_and_peace
23
- zip.add_stored_entry filename: 'text2.txt', crc32: Zlib.crc32(war_and_peace), size: war_and_peace.bytesize
24
- zip << war_and_peace
25
- zip.add_stored_entry filename: 'text3.txt', crc32: Zlib.crc32(war_and_peace), size: war_and_peace.bytesize
26
- zip << war_and_peace
27
- end
28
- zipfile.rewind
29
-
30
- recovered_entries = described_class.read_zip_straight_ahead(io: zipfile)
31
- expect(recovered_entries.length).to eq(3)
32
- recovered_entries.each do |entry|
33
- expect(entry.storage_mode).to eq(0)
34
- expect(entry.compressed_size).to eq(496006)
35
- expect(entry.uncompressed_size).to eq(496006)
36
- end
37
-
38
- first, second, third = recovered_entries
39
- expect(first.compressed_data_offset).to eq(39)
40
- expect(second.compressed_data_offset).to eq(496084)
41
- expect(third.compressed_data_offset).to eq(992129)
42
-
43
- recovered_entries.each do |entry|
44
- zipfile.seek(entry.compressed_data_offset)
45
- expect(zipfile.read(5)).to eq(war_and_peace[0...5])
46
- end
47
- end
48
-
49
- it 'recovers an entry that uses Zip64 extra fields' do
50
- zipfile = StringIO.new
51
- w = ZipTricks::ZipWriter.new
52
- w.write_local_file_header(io: zipfile, filename: 'big.bin', compressed_size: 0xFFFFFFFFFF, uncompressed_size: 0xFFFFFFFFF,
53
- crc32: 0, gp_flags: 0, mtime: Time.now, storage_mode: 0)
54
- zipfile.rewind
55
- recovered_entries = described_class.read_zip_straight_ahead(io: zipfile)
56
- expect(recovered_entries.length).to eq(1)
57
- entry = recovered_entries.shift
58
- expect(entry.compressed_size).to eq(0xFFFFFFFFFF)
59
- end
60
-
61
- it 'raises when an entry uses a data descriptor' do
62
- zipfile = StringIO.new
63
- ZipTricks::Streamer.open(zipfile) do |zip|
64
- zip.write_deflated_file('war-and-peace.txt') do |sink|
65
- sink << File.read(__dir__ + '/war-and-peace.txt')
66
- end
67
- end
68
- zipfile.rewind
69
-
70
- expect {
71
- described_class.read_zip_straight_ahead(io: zipfile)
72
- }.to raise_error(described_class::UnsupportedFeature)
73
- end
74
- end
75
-
76
- context 'with a ZIP file where the size of the central directory is recorded incorrectly, and has a wrong value' do
77
- it 'is still able to read the entries' do
78
- # Purposefully create a ZIP that we will damage after
79
- zipfile = StringIO.new
80
- tolstoy = File.read(__dir__ + '/war-and-peace.txt')
81
- tolstoy.force_encoding(Encoding::BINARY)
82
-
83
- # Make a one-off Writer that corrupts the size of the central directory and says there is more data
84
- # in it than there actually is
85
- class EvilWriter < ZipTricks::ZipWriter
86
- def write_end_of_central_directory(**kwargs)
87
- kwargs[:central_directory_size] = kwargs[:central_directory_size] + 64 # Pretend there has to be more data
88
- super(**kwargs)
89
- end
90
- end
91
-
92
- ZipTricks::Streamer.open(zipfile, writer: EvilWriter.new) do |zip|
93
- zip.write_deflated_file('text-1.txt') { |sink| sink << tolstoy }
94
- zip.write_deflated_file('text-2.txt') { |sink| sink << tolstoy }
95
- end
96
-
97
- # Find the start of the EOCD record (signature, then the information about disk numbers and the information
98
- # about the file counts
99
- eocd_start_marker = [0x06054b50, 0, 0, 2, 2].pack('Vvvvv')
100
- eocd_offset = zipfile.string.index(eocd_start_marker)
101
- expect(eocd_offset).not_to be_nil
102
-
103
- # Seek to the offset where the central directory size is going to be (just after the string we looked for)
104
- zipfile.seek(eocd_offset + eocd_start_marker.bytesize)
105
- # and overwrite it.
106
- zipfile.write([118].pack('V'))
107
- zipfile.rewind
108
-
109
- entries = ZipTricks::FileReader.read_zip_structure(io: zipfile)
110
-
111
- expect(entries.length).to eq(2)
112
- end
113
- end
114
-
115
- context 'with an end-to-end ZIP file to read' do
116
- it 'reads and uncompresses the file written deflated with data descriptors' do
117
- zipfile = StringIO.new
118
- tolstoy = File.read(__dir__ + '/war-and-peace.txt')
119
- tolstoy.force_encoding(Encoding::BINARY)
120
-
121
- ZipTricks::Streamer.open(zipfile) do |zip|
122
- zip.write_deflated_file('war-and-peace.txt') do |sink|
123
- sink << tolstoy
124
- end
125
- end
126
-
127
- entries = described_class.read_zip_structure(io: zipfile)
128
- expect(entries.length).to eq(1)
129
-
130
- entry = entries.first
131
-
132
- readback = ''
133
- reader = entry.extractor_from(zipfile)
134
- readback << reader.extract(10) until reader.eof?
135
-
136
- expect(readback.bytesize).to eq(tolstoy.bytesize)
137
- expect(readback[0..10]).to eq(tolstoy[0..10])
138
- expect(readback[-10..-1]).to eq(tolstoy[-10..-1])
139
- end
140
-
141
- it 'performs local file header reads by default' do
142
- zipfile = StringIO.new
143
- tolstoy = File.read(__dir__ + '/war-and-peace.txt')
144
- tolstoy.force_encoding(Encoding::BINARY)
145
-
146
- ZipTricks::Streamer.open(zipfile) do |zip|
147
- 40.times do |i|
148
- zip.write_deflated_file('war-and-peace-%d.txt' % i) { |sink| sink << tolstoy }
149
- end
150
- end
151
- zipfile.rewind
152
-
153
- read_monitor = ReadMonitor.new(zipfile)
154
- entries = described_class.read_zip_structure(io: read_monitor, read_local_headers: true)
155
- expect(read_monitor.num_reads).to eq(44)
156
- end
157
-
158
- it 'performs local file header reads when `read_local_headers` is set to true' do
159
- zipfile = StringIO.new
160
- tolstoy = File.read(__dir__ + '/war-and-peace.txt')
161
- tolstoy.force_encoding(Encoding::BINARY)
162
-
163
- ZipTricks::Streamer.open(zipfile) do |zip|
164
- 40.times do |i|
165
- zip.write_deflated_file('war-and-peace-%d.txt' % i) { |sink| sink << tolstoy }
166
- end
167
- end
168
- zipfile.rewind
169
-
170
- read_monitor = ReadMonitor.new(zipfile)
171
- entries = described_class.read_zip_structure(io: read_monitor, read_local_headers: true)
172
- expect(read_monitor.num_reads).to eq(44)
173
-
174
- expect(entries.length).to eq(40)
175
- entry = entries.first
176
- expect(entry).to be_known_offset
177
- end
178
-
179
- it 'performs a limited number of reads when `read_local_headers` is set to false' do
180
- zipfile = StringIO.new
181
- tolstoy = File.read(__dir__ + '/war-and-peace.txt')
182
- tolstoy.force_encoding(Encoding::BINARY)
183
-
184
- ZipTricks::Streamer.open(zipfile) do |zip|
185
- 40.times do |i|
186
- zip.write_deflated_file('war-and-peace-%d.txt' % i) { |sink| sink << tolstoy }
187
- end
188
- end
189
- zipfile.rewind
190
- read_monitor = ReadMonitor.new(zipfile)
191
-
192
- entries = described_class.read_zip_structure(io: read_monitor, read_local_headers: false)
193
-
194
- expect(read_monitor.num_reads).to eq(4)
195
- expect(entries.length).to eq(40)
196
- entry = entries.first
197
- expect(entry).not_to be_known_offset
198
- expect {
199
- entry.compressed_data_offset
200
- }.to raise_error(/read/)
201
- end
202
-
203
- it 'reads the file written stored with data descriptors' do
204
- zipfile = StringIO.new
205
- tolstoy = File.read(__dir__ + '/war-and-peace.txt')
206
- ZipTricks::Streamer.open(zipfile) do |zip|
207
- zip.write_stored_file('war-and-peace.txt') do |sink|
208
- sink << tolstoy
209
- end
210
- end
211
-
212
- entries = described_class.read_zip_structure(io: zipfile)
213
- expect(entries.length).to eq(1)
214
-
215
- entry = entries.first
216
-
217
- readback = entry.extractor_from(zipfile).extract
218
- expect(readback.bytesize).to eq(tolstoy.bytesize)
219
- expect(readback[0..10]).to eq(tolstoy[0..10])
220
- end
221
- end
222
-
223
- describe '#get_compressed_data_offset' do
224
- it 'reads the offset for an entry having Zip64 extra fields' do
225
- w = ZipTricks::ZipWriter.new
226
- out = StringIO.new
227
- out << Random.new.bytes(7656177)
228
- w.write_local_file_header(io: out, filename: 'some file',
229
- compressed_size: 0xFFFFFFFF + 5, uncompressed_size: 0xFFFFFFFFF, crc32: 123, gp_flags: 4,
230
- mtime: Time.now, storage_mode: 8)
231
-
232
- out.rewind
233
-
234
- compressed_data_offset = subject.get_compressed_data_offset(io: out, local_file_header_offset: 7656177)
235
- expect(compressed_data_offset).to eq(7656236)
236
- end
237
-
238
- it 'reads the offset for an entry having a long name' do
239
- w = ZipTricks::ZipWriter.new
240
- out = StringIO.new
241
- out << Random.new.bytes(7)
242
- w.write_local_file_header(io: out, filename: 'This is a file with a ridiculously long name.doc',
243
- compressed_size: 10, uncompressed_size: 15, crc32: 123, gp_flags: 4,
244
- mtime: Time.now, storage_mode: 8)
245
-
246
- out.rewind
247
-
248
- compressed_data_offset = subject.get_compressed_data_offset(io: out, local_file_header_offset: 7)
249
- expect(compressed_data_offset).to eq(85)
250
- end
251
- end
252
-
253
- it 'is able to latch to the EOCD location even if the signature for the EOCD record appears all over the ZIP' do
254
- # A VERY evil ZIP file which has this signature all over
255
- eocd_sig = [0x06054b50].pack('V')
256
- evil_str = "#{eocd_sig} and #{eocd_sig}"
257
-
258
- z = StringIO.new
259
- w = ZipTricks::ZipWriter.new
260
- w.write_local_file_header(io: z, filename: evil_str, compressed_size: evil_str.bytesize,
261
- uncompressed_size: evil_str.bytesize, crc32: 0x06054b50, gp_flags: 0, mtime: Time.now, storage_mode: 0)
262
- z << evil_str
263
- where = z.tell
264
- w.write_central_directory_file_header(io: z, local_file_header_location: 0, gp_flags: 0, storage_mode: 0,
265
- filename: evil_str, compressed_size: evil_str.bytesize,
266
- uncompressed_size: evil_str.bytesize, mtime: Time.now, crc32: 0x06054b50)
267
- w.write_end_of_central_directory(io: z, start_of_central_directory_location: where,
268
- central_directory_size: z.tell - where, num_files_in_archive: 1, comment: evil_str)
269
-
270
- z.rewind
271
- entries = described_class.read_zip_structure(io: z)
272
- expect(entries.length).to eq(1)
273
- end
274
-
275
- it 'can handle a Zip64 central directory fields that only contains the required fields (substitutes for standard fields)' do
276
- # In this example central directory, 2 entries contain Zip64 extra where only the local header offset is set (8 bytes each)
277
- # This is the exceptional case where we have to poke at a private method directly
278
- File.open(__dir__ + '/cdir_entry_with_partial_use_of_zip64_extra_fields.bin', 'rb') do |f|
279
- reader = described_class.new
280
- entry = reader.send(:read_cdir_entry, f)
281
- expect(entry.local_file_header_offset).to eq(4312401349)
282
- expect(entry.filename).to eq('Motorhead - Ace Of Spades.srt')
283
- expect(entry.compressed_size).to eq(69121)
284
- expect(entry.uncompressed_size).to eq(69121)
285
- end
286
- end
287
- end
@@ -1,34 +0,0 @@
1
- require_relative '../spec_helper'
2
-
3
- describe ZipTricks::RackBody do
4
- it 'is usable as a Rack response body, supports each() and close()' do
5
- output_buf = Tempfile.new('output')
6
-
7
- file_body = SecureRandom.random_bytes(1024 * 1024 + 8981)
8
-
9
- body = described_class.new do | zip |
10
- zip.add_stored_entry(filename: "A file", size: file_body.bytesize, crc32: Zlib.crc32(file_body))
11
- zip << file_body
12
- end
13
-
14
- body.each do | some_data |
15
- output_buf << some_data
16
- end
17
- body.close
18
-
19
- output_buf.rewind
20
- expect(output_buf.size).to eq(1057696)
21
-
22
- per_filename = {}
23
- Zip::File.open(output_buf.path) do |zip_file|
24
- # Handle entries one by one
25
- zip_file.each do |entry|
26
- # The entry name gets returned with a binary encoding, we have to force it back.
27
- per_filename[entry.name] = entry.get_input_stream.read
28
- end
29
- end
30
-
31
- expect(per_filename).to have_key('A file')
32
- expect(per_filename['A file'].bytesize).to eq(file_body.bytesize)
33
- end
34
- end
@@ -1,125 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ZipTricks::RemoteIO do
4
-
5
- context 'working with the fetcher object' do
6
- it 'asks the fetcher object to obtain the object size and the actual data when reading' do
7
- mock_fetcher = double(request_object_size: 120, request_range: 'abc')
8
- subject = described_class.new(mock_fetcher)
9
- expect(subject.read(3)).to eq('abc')
10
- end
11
- end
12
-
13
- context 'when it internally addresses a remote resource' do
14
- it 'requests the size of the resource once via #request_object_size and does neet to read if resource is empty' do
15
- subject = described_class.new
16
- expect(subject).to receive(:request_object_size).and_return(0)
17
- expect(subject.read).to be_nil
18
- end
19
-
20
- it 'performs remote reads when repeatedly requesting the same chunk, via #request_range' do
21
- subject = described_class.new
22
-
23
- expect(subject).to receive(:request_object_size).and_return(120)
24
- allow(subject).to receive(:request_range) {|range|
25
- expect(range).to eq(5..14)
26
- Random.new.bytes(10)
27
- }
28
- 20.times do
29
- subject.seek(5, IO::SEEK_SET)
30
- subject.read(10)
31
- end
32
- end
33
- end
34
-
35
- describe '#seek' do
36
- context 'with an unsupported mode' do
37
- it 'raises an error' do
38
- uncap = described_class.new
39
- expect {
40
- uncap.seek(123, :UNSUPPORTED)
41
- }.to raise_error(/unsupported/i)
42
- end
43
- end
44
-
45
- context 'with SEEK_SET mode' do
46
- it 'returns the offset of 10 when asked to seek to 10' do
47
- uncap = described_class.new
48
- expect(uncap).to receive(:request_object_size).and_return(100)
49
- mode = IO::SEEK_SET
50
- expect(uncap.seek(10, mode)).to eq(0)
51
- end
52
- end
53
- end
54
-
55
- describe '#read' do
56
- before :each do
57
- @buf = Tempfile.new('simulated-http')
58
- @buf.binmode
59
- 5.times { @buf << Random.new.bytes(1024 * 1024 * 3) }
60
- @buf.rewind
61
-
62
- @subject = described_class.new
63
-
64
- allow(@subject).to receive(:request_object_size).and_return(@buf.size)
65
- allow(@subject).to receive(:request_range) {|range|
66
- @buf.read[range].tap { @buf.rewind }
67
- }
68
- end
69
-
70
- after :each do
71
- if @buf
72
- @buf.close; @buf.unlink
73
- end
74
- end
75
-
76
- context 'without arguments' do
77
- it 'reads the entire buffer and alters the position pointer' do
78
- expect(@subject.tell).to eq(0)
79
- read = @subject.read
80
- expect(read.bytesize).to eq(@buf.size)
81
- expect(@subject.tell).to eq(@buf.size)
82
- end
83
- end
84
-
85
- context 'with length' do
86
- it 'supports an unlimited number of reads of size 0 and does not perform remote fetches for them' do
87
- expect(@subject).not_to receive(:request_range)
88
- 20.times do
89
- data = @subject.read(0)
90
- expect(data).to eq('')
91
- end
92
- end
93
-
94
- it 'returns exact amount of bytes at the start of the buffer' do
95
- bytes_read = @subject.read(10)
96
- expect(@subject.tell).to eq(10)
97
- @buf.seek(0)
98
- expect(bytes_read).to eq(@buf.read(10))
99
- end
100
-
101
- it 'returns exact amount of bytes from the middle of the buffer' do
102
- @subject.seek(456, IO::SEEK_SET)
103
-
104
- bytes_read = @subject.read(10)
105
- expect(@subject.tell).to eq(456+10)
106
-
107
- @buf.seek(456)
108
- expect(bytes_read).to eq(@buf.read(10))
109
- end
110
-
111
- it 'returns the last N bytes it can read' do
112
- at_end = @buf.size - 4
113
- @subject.seek(at_end, IO::SEEK_SET)
114
-
115
- expect(@subject.tell).to eq(15728636)
116
- bytes_read = @subject.read(10)
117
- expect(@subject.tell).to eq(@buf.size) # Should have moved the pos pointer to the end
118
-
119
- expect(bytes_read.bytesize).to eq(4)
120
-
121
- expect(@subject.tell).to eq(@buf.size)
122
- end
123
- end
124
- end
125
- end