zip_tricks 4.2.3 → 4.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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