zip_tricks 4.4.2 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Stashes a block given by the Rack webserver when calling each() on a body, and calls
2
4
  # that block every time it is written to using :<< (shovel). Poses as an IO for rubyzip.
5
+
3
6
  class ZipTricks::BlockWrite
4
7
  # The block is the block given to each() of the Rack body, or other block you want
5
8
  # to receive the string chunks written by the zip compressor.
@@ -8,26 +11,33 @@ class ZipTricks::BlockWrite
8
11
  end
9
12
 
10
13
  # Make sure those methods raise outright
11
- [:seek, :pos=, :to_s].each do |m|
12
- define_method(m) do |*args|
14
+ %i[seek pos= to_s].each do |m|
15
+ define_method(m) do |*_args|
13
16
  raise "#{m} not supported - this IO adapter is non-rewindable"
14
17
  end
15
18
  end
16
19
 
17
- # Every time this object gets written to, call the Rack body each() block with the bytes given instead.
20
+ # Every time this object gets written to, call the Rack body each() block
21
+ # with the bytes given instead.
18
22
  def <<(buf)
19
23
  return if buf.nil?
20
24
 
21
25
  # Ensure we ALWAYS write in binary encoding.
22
- encoded = if buf.encoding != Encoding::BINARY
23
- # If we got a frozen string we can't force_encoding on it
24
- buf.force_encoding(Encoding::BINARY) rescue buf.dup.force_encoding(Encoding::BINARY)
25
- else
26
- buf
27
- end
26
+ encoded =
27
+ if buf.encoding != Encoding::BINARY
28
+ # If we got a frozen string we can't force_encoding on it
29
+ begin
30
+ buf.force_encoding(Encoding::BINARY)
31
+ rescue
32
+ buf.dup.force_encoding(Encoding::BINARY)
33
+ end
34
+ else
35
+ buf
36
+ end
28
37
 
29
38
  # buf.dup.force_encoding(Encoding::BINARY)
30
- return if encoded.bytesize.zero? # Zero-size output has a special meaning when using chunked encoding
39
+ # Zero-size output has a special meaning when using chunked encoding
40
+ return if encoded.bytesize.zero?
31
41
 
32
42
  @block.call(encoded)
33
43
  self
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Naming/ConstantName
4
+
1
5
  require 'stringio'
2
6
 
3
7
  # A very barebones ZIP file reader. Is made for maximum interoperability, but at the same
@@ -41,20 +45,25 @@ require 'stringio'
41
45
  #
42
46
  # ## Mode of operation
43
47
  #
44
- # By default, `FileReader` _ignores_ the data in local file headers (as it is often unreliable).
45
- # It reads the ZIP file "from the tail", finds the end-of-central-directory signatures, then
46
- # reads the central directory entries, reconstitutes the entries with their filenames, attributes
47
- # and so on, and sets these entries up with the absolute _offsets_ into the source file/IO object.
48
- # These offsets can then be used to extract the actual compressed data of the files and to expand it.
48
+ # By default, `FileReader` _ignores_ the data in local file headers (as it is
49
+ # often unreliable). It reads the ZIP file "from the tail", finds the
50
+ # end-of-central-directory signatures, then reads the central directory entries,
51
+ # reconstitutes the entries with their filenames, attributes and so on, and
52
+ # sets these entries up with the absolute _offsets_ into the source file/IO object.
53
+ # These offsets can then be used to extract the actual compressed data of
54
+ # the files and to expand it.
49
55
  #
50
56
  # ## Recovering damaged or incomplete ZIP files
51
57
  #
52
- # If the ZIP file you are trying to read does not contain the central directory records `read_zip_structure`
53
- # will not work, since it starts the read process from the EOCD marker at the end of the central directory
54
- # and then crawls "back" in the IO to figure out the rest. You can explicitly apply a fallback for reading the
55
- # archive "straight ahead" instead using `read_zip_straight_ahead` - the method will instead scan your IO from
56
- # the very start, skipping over the actual entry data. This is less efficient than central directory parsing since
58
+ # If the ZIP file you are trying to read does not contain the central directory
59
+ # records `read_zip_structure` will not work, since it starts the read process
60
+ # from the EOCD marker at the end of the central directory and then crawls
61
+ # "back" in the IO to figure out the rest. You can explicitly apply a fallback
62
+ # for reading the archive "straight ahead" instead using `read_zip_straight_ahead`
63
+ # - the method will instead scan your IO from the very start, skipping over
64
+ # the actual entry data. This is less efficient than central directory parsing since
57
65
  # it involves a much larger number of reads (1 read from the IO per entry in the ZIP).
66
+
58
67
  class ZipTricks::FileReader
59
68
  require_relative 'file_reader/stored_reader'
60
69
  require_relative 'file_reader/inflating_reader'
@@ -64,17 +73,17 @@ class ZipTricks::FileReader
64
73
  InvalidStructure = Class.new(ReadError)
65
74
  LocalHeaderPending = Class.new(StandardError) do
66
75
  def message
67
- "The compressed data offset is not available (local header has not been read)"
76
+ 'The compressed data offset is not available (local header has not been read)'
68
77
  end
69
78
  end
70
79
  MissingEOCD = Class.new(StandardError) do
71
80
  def message
72
- "Could not find the EOCD signature in the buffer - maybe a malformed ZIP file"
81
+ 'Could not find the EOCD signature in the buffer - maybe a malformed ZIP file'
73
82
  end
74
83
  end
75
-
84
+
76
85
  private_constant :StoredReader, :InflatingReader
77
-
86
+
78
87
  # Represents a file within the ZIP archive being read
79
88
  class ZipEntry
80
89
  # @return [Fixnum] bit-packed version signature of the program that made the archive
@@ -137,28 +146,29 @@ class ZipTricks::FileReader
137
146
  when 0
138
147
  StoredReader.new(from_io, compressed_size)
139
148
  else
140
- raise UnsupportedFeature, "Unsupported storage mode for reading - %d" % storage_mode
149
+ raise UnsupportedFeature, format('Unsupported storage mode for reading - %d',
150
+ storage_mode)
141
151
  end
142
152
  end
143
-
153
+
144
154
  # @return [Fixnum] at what offset you should start reading
145
155
  # for the compressed data in your original IO object
146
156
  def compressed_data_offset
147
- @compressed_data_offset or raise LocalHeaderPending
157
+ @compressed_data_offset || raise(LocalHeaderPending)
148
158
  end
149
-
159
+
150
160
  # Tells whether the compressed data offset is already known for this entry
151
161
  # @return [Boolean]
152
162
  def known_offset?
153
163
  !@compressed_data_offset.nil?
154
164
  end
155
-
165
+
156
166
  # Tells whether the entry uses a data descriptor (this is defined
157
167
  # by bit 3 in the GP flags).
158
168
  def uses_data_descriptor?
159
169
  (gp_flags & 0x0008) == 0x0008
160
170
  end
161
-
171
+
162
172
  # Sets the offset at which the compressed data for this file starts in the ZIP.
163
173
  # By default, the value will be set by the Reader for you. If you use delayed
164
174
  # reading, you need to set it by using the `get_compressed_data_offset` on the Reader:
@@ -191,12 +201,14 @@ class ZipTricks::FileReader
191
201
  eocd_offset = get_eocd_offset(io, zip_file_size)
192
202
 
193
203
  zip64_end_of_cdir_location = get_zip64_eocd_location(io, eocd_offset)
194
- num_files, cdir_location, cdir_size = if zip64_end_of_cdir_location
195
- num_files_and_central_directory_offset_zip64(io, zip64_end_of_cdir_location)
196
- else
197
- num_files_and_central_directory_offset(io, eocd_offset)
198
- end
199
- log { 'Located the central directory start at %d' % cdir_location }
204
+ num_files, cdir_location, _cdir_size =
205
+ if zip64_end_of_cdir_location
206
+ num_files_and_central_directory_offset_zip64(io, zip64_end_of_cdir_location)
207
+ else
208
+ num_files_and_central_directory_offset(io, eocd_offset)
209
+ end
210
+
211
+ log { format('Located the central directory start at %d', cdir_location) }
200
212
  seek(io, cdir_location)
201
213
 
202
214
  # Read the entire central directory AND anything behind it, in one fell swoop.
@@ -214,55 +226,77 @@ class ZipTricks::FileReader
214
226
  # the central directory size alltogether.
215
227
  central_directory_str = io.read # and not read_n(io, cdir_size), see above
216
228
  central_directory_io = StringIO.new(central_directory_str)
217
- log { 'Read %d bytes with central directory + EOCD record and locator' % central_directory_str.bytesize }
229
+ log do
230
+ format('Read %d bytes with central directory + EOCD record and locator',
231
+ central_directory_str.bytesize)
232
+ end
218
233
 
219
234
  entries = (0...num_files).map do |entry_n|
220
- log { 'Reading the central directory entry %d starting at offset %d' % [entry_n, cdir_location + central_directory_io.tell] }
235
+ offset_location = cdir_location + central_directory_io.tell
236
+ log do
237
+ format('Reading the central directory entry %d starting at offset %d',
238
+ entry_n, offset_location)
239
+ end
221
240
  read_cdir_entry(central_directory_io)
222
241
  end
223
-
242
+
224
243
  read_local_headers(entries, io) if read_local_headers
225
-
244
+
226
245
  entries
227
246
  end
228
247
 
229
- # Sometimes you might encounter truncated ZIP files, which do not contain any central directory
230
- # whatsoever - or where the central directory is truncated. In that case, employing the technique
231
- # of reading the ZIP "from the end" is impossible, and the only recourse is reading each local file header
232
- # in sucession. If the entries in such a ZIP use data descriptors, you would need to scan after the entry until
233
- # you encounter the data descriptor signature - and that might be unreliable at best. Therefore, this reading
234
- # technique does not support data descriptors. It can however recover the entries you still can read if these
235
- # entries contain all the necessary information about the contained file.
248
+ # Sometimes you might encounter truncated ZIP files, which do not contain
249
+ # any central directory whatsoever - or where the central directory is
250
+ # truncated. In that case, employing the technique of reading the ZIP
251
+ # "from the end" is impossible, and the only recourse is reading each
252
+ # local file header in sucession. If the entries in such a ZIP use data
253
+ # descriptors, you would need to scan after the entry until you encounter
254
+ # the data descriptor signature - and that might be unreliable at best.
255
+ # Therefore, this reading technique does not support data descriptors.
256
+ # It can however recover the entries you still can read if these entries
257
+ # contain all the necessary information about the contained file.
236
258
  #
237
- # @param io[#tell, #read, #seek] the IO-ish object to read the local file headers from
238
- # @return [Array<ZipEntry>] an array of entries that could be recovered before hitting EOF
259
+ # @param io[#tell, #read, #seek] the IO-ish object to read the local file
260
+ # headers from @return [Array<ZipEntry>] an array of entries that could be
261
+ # recovered before hitting EOF
239
262
  def read_zip_straight_ahead(io:)
240
263
  entries = []
241
264
  loop do
242
265
  cur_offset = io.tell
243
266
  entry = read_local_file_header(io: io)
244
267
  if entry.uses_data_descriptor?
245
- raise UnsupportedFeature, "The local file header at #{cur_offset} uses a data descriptor and the start of next entry cannot be found"
268
+ raise UnsupportedFeature, "The local file header at #{cur_offset} uses \
269
+ a data descriptor and the start of next entry \
270
+ cannot be found"
246
271
  end
247
272
  entries << entry
248
273
  next_local_header_offset = entry.compressed_data_offset + entry.compressed_size
249
- log { 'Recovered a local file file header at offset %d, seeking to the next at %d' % [cur_offset, next_local_header_offset] }
274
+ log do
275
+ format('Recovered a local file file header at offset %d, seeking to the next at %d',
276
+ cur_offset, next_local_header_offset)
277
+ end
250
278
  seek(io, next_local_header_offset) # Seek to the next entry, and raise if seek is impossible
251
279
  end
252
280
  entries
253
281
  rescue ReadError
254
- log { 'Got a read/seek error after reaching %d, no more entries can be recovered' % cur_offset }
282
+ log do
283
+ format('Got a read/seek error after reaching %d, no more entries can be recovered',
284
+ cur_offset)
285
+ end
255
286
  entries
256
287
  end
257
-
258
- # Parse the local header entry and get the offset in the IO at which the actual compressed data of the
259
- # file starts within the ZIP.
260
- # The method will eager-read the entire local header for the file (the maximum size the local header may use),
261
- # starting at the given offset, and will then compute its size. That size plus the local header offset
262
- # given will be the compressed data offset of the entry (read starting at this offset to get the data).
288
+
289
+ # Parse the local header entry and get the offset in the IO at which the
290
+ # actual compressed data of the file starts within the ZIP.
291
+ # The method will eager-read the entire local header for the file
292
+ # (the maximum size the local header may use), starting at the given offset,
293
+ # and will then compute its size. That size plus the local header offset
294
+ # given will be the compressed data offset of the entry (read starting at
295
+ # this offset to get the data).
263
296
  #
264
297
  # @param io[#read] an IO-ish object the ZIP file can be read from
265
- # @return [Array<ZipEntry, Fixnum>] the parsed local header entry and the compressed data offset
298
+ # @return [Array<ZipEntry, Fixnum>] the parsed local header entry and
299
+ # the compressed data offset
266
300
  def read_local_file_header(io:)
267
301
  local_file_header_offset = io.tell
268
302
 
@@ -270,7 +304,7 @@ class ZipTricks::FileReader
270
304
  # including any headroom for extra fields etc.
271
305
  local_file_header_str_plus_headroom = io.read(MAX_LOCAL_HEADER_SIZE)
272
306
  raise ReadError if local_file_header_str_plus_headroom.nil? # reached EOF
273
-
307
+
274
308
  io_starting_at_local_header = StringIO.new(local_file_header_str_plus_headroom)
275
309
 
276
310
  assert_signature(io_starting_at_local_header, 0x04034b50)
@@ -288,19 +322,22 @@ class ZipTricks::FileReader
288
322
  extra_size = read_2b(io_starting_at_local_header)
289
323
  e.filename = read_n(io_starting_at_local_header, filename_size)
290
324
  extra_fields_str = read_n(io_starting_at_local_header, extra_size)
291
-
325
+
292
326
  # Parse out the extra fields
293
327
  extra_table = parse_out_extra_fields(extra_fields_str)
294
-
328
+
295
329
  # ...of which we really only need the Zip64 extra
296
330
  if zip64_extra_contents = extra_table[1]
297
331
  # If the Zip64 extra is present, we let it override all
298
332
  # the values fetched from the conventional header
299
333
  zip64_extra = StringIO.new(zip64_extra_contents)
300
- log { 'Will read Zip64 extra data from local header field for %s, %d bytes' % [e.filename, zip64_extra.size] }
334
+ log do
335
+ format('Will read Zip64 extra data from local header field for %s, %d bytes',
336
+ e.filename, zip64_extra.size)
337
+ end
301
338
  # Now here be dragons. The APPNOTE specifies that
302
339
  #
303
- # > The order of the fields in the ZIP64 extended
340
+ # > The order of the fields in the ZIP64 extended
304
341
  # > information record is fixed, but the fields will
305
342
  # > only appear if the corresponding Local or Central
306
343
  # > directory record field is set to 0xFFFF or 0xFFFFFFFF.
@@ -317,14 +354,17 @@ class ZipTricks::FileReader
317
354
  e
318
355
  end
319
356
 
320
- # Get the offset in the IO at which the actual compressed data of the file starts within the ZIP.
321
- # The method will eager-read the entire local header for the file (the maximum size the local header may use),
322
- # starting at the given offset, and will then compute its size. That size plus the local header offset
323
- # given will be the compressed data offset of the entry (read starting at this offset to get the data).
357
+ # Get the offset in the IO at which the actual compressed data of the file
358
+ # starts within the ZIP. The method will eager-read the entire local header
359
+ # for the file (the maximum size the local header may use), starting at the
360
+ # given offset, and will then compute its size. That size plus the local
361
+ # header offset given will be the compressed data offset of the entry
362
+ # (read starting at this offset to get the data).
324
363
  #
325
364
  # @param io[#seek, #read] an IO-ish object the ZIP file can be read from
326
- # @param local_header_offset[Fixnum] absolute offset (0-based) where the local file header is supposed to begin
327
- # @return [Fixnum] absolute offset (0-based) of where the compressed data begins for this file within the ZIP
365
+ # @param local_header_offset[Fixnum] absolute offset (0-based) where the
366
+ # local file header is supposed to begin @return [Fixnum] absolute offset
367
+ # (0-based) of where the compressed data begins for this file within the ZIP
328
368
  def get_compressed_data_offset(io:, local_file_header_offset:)
329
369
  seek(io, local_file_header_offset)
330
370
  entry_recovered_from_local_file_header = read_local_file_header(io: io)
@@ -350,17 +390,21 @@ class ZipTricks::FileReader
350
390
  def self.read_zip_straight_ahead(**options)
351
391
  new.read_zip_straight_ahead(**options)
352
392
  end
353
-
393
+
354
394
  private
355
395
 
356
396
  def read_local_headers(entries, io)
357
397
  entries.each_with_index do |entry, i|
358
- log { 'Reading the local header for entry %d at offset %d' % [i, entry.local_file_header_offset] }
359
- off = get_compressed_data_offset(io: io, local_file_header_offset: entry.local_file_header_offset)
398
+ log do
399
+ format('Reading the local header for entry %d at offset %d',
400
+ i, entry.local_file_header_offset)
401
+ end
402
+ off = get_compressed_data_offset(io: io,
403
+ local_file_header_offset: entry.local_file_header_offset)
360
404
  entry.compressed_data_offset = off
361
405
  end
362
406
  end
363
-
407
+
364
408
  def skip_ahead_2(io)
365
409
  skip_ahead_n(io, 2)
366
410
  end
@@ -375,13 +419,17 @@ class ZipTricks::FileReader
375
419
 
376
420
  def seek(io, absolute_pos)
377
421
  io.seek(absolute_pos, IO::SEEK_SET)
378
- raise ReadError, "Expected to seek to #{absolute_pos} but only got to #{io.tell}" unless absolute_pos == io.tell
422
+ unless absolute_pos == io.tell
423
+ raise ReadError,
424
+ "Expected to seek to #{absolute_pos} but only \
425
+ got to #{io.tell}"
426
+ end
379
427
  nil
380
428
  end
381
429
 
382
430
  def assert_signature(io, signature_magic_number)
383
- packed = [signature_magic_number].pack(C_V)
384
431
  readback = read_4b(io)
432
+ # Rubocop: Use a guard clause instead of wrapping the code inside a conditional expression
385
433
  if readback != signature_magic_number
386
434
  expected = '0x0' + signature_magic_number.to_s(16)
387
435
  actual = '0x0' + readback.to_s(16)
@@ -394,15 +442,21 @@ class ZipTricks::FileReader
394
442
  io.seek(io.tell + n, IO::SEEK_SET)
395
443
  pos_after = io.tell
396
444
  delta = pos_after - pos_before
397
- raise ReadError, "Expected to seek #{n} bytes ahead, but could only seek #{delta} bytes ahead" unless delta == n
445
+ unless delta == n
446
+ raise ReadError, "Expected to seek #{n} bytes ahead, but could \
447
+ only seek #{delta} bytes ahead"
448
+ end
398
449
  nil
399
450
  end
400
451
 
401
452
  def read_n(io, n_bytes)
402
- io.read(n_bytes).tap {|d|
453
+ io.read(n_bytes).tap do |d|
403
454
  raise ReadError, "Expected to read #{n_bytes} bytes, but the IO was at the end" if d.nil?
404
- raise ReadError, "Expected to read #{n_bytes} bytes, read #{d.bytesize}" unless d.bytesize == n_bytes
405
- }
455
+ unless d.bytesize == n_bytes
456
+ raise ReadError, "Expected to read #{n_bytes} bytes, \
457
+ read #{d.bytesize}"
458
+ end
459
+ end
406
460
  end
407
461
 
408
462
  def read_2b(io)
@@ -418,8 +472,12 @@ class ZipTricks::FileReader
418
472
  end
419
473
 
420
474
  def read_cdir_entry(io)
475
+ # Rubocop: convention: Assignment Branch Condition size for
476
+ # read_cdir_entry is too high. [45.66/15]
477
+ # Rubocop: convention: Method has too many lines. [30/10]
421
478
  assert_signature(io, 0x02014b50)
422
479
  ZipEntry.new.tap do |e|
480
+ # Rubocop: convention: Block has too many lines. [27/25]
423
481
  e.made_by = read_2b(io)
424
482
  e.version_needed_to_extract = read_2b(io)
425
483
  e.gp_flags = read_2b(io)
@@ -447,24 +505,35 @@ class ZipTricks::FileReader
447
505
  extra_table = parse_out_extra_fields(extras)
448
506
 
449
507
  # ...of which we really only need the Zip64 extra
450
- if zip64_extra_contents = extra_table[1]
508
+ if zip64_extra_contents ||= extra_table[1]
451
509
  # If the Zip64 extra is present, we let it override all
452
510
  # the values fetched from the conventional header
453
511
  zip64_extra = StringIO.new(zip64_extra_contents)
454
- log { 'Will read Zip64 extra data for %s, %d bytes' % [e.filename, zip64_extra.size] }
512
+ log do
513
+ format('Will read Zip64 extra data for %s, %d bytes',
514
+ e.filename, zip64_extra.size)
515
+ end
455
516
  # Now here be dragons. The APPNOTE specifies that
456
517
  #
457
- # > The order of the fields in the ZIP64 extended
518
+ # > The order of the fields in the ZIP64 extended
458
519
  # > information record is fixed, but the fields will
459
520
  # > only appear if the corresponding Local or Central
460
521
  # > directory record field is set to 0xFFFF or 0xFFFFFFFF.
461
522
  #
462
523
  # It means that before we read this stuff we need to check if the previously-read
463
524
  # values are at overflow, and only _then_ proceed to read them. Bah.
464
- e.uncompressed_size = read_8b(zip64_extra) if e.uncompressed_size == 0xFFFFFFFF
465
- e.compressed_size = read_8b(zip64_extra) if e.compressed_size == 0xFFFFFFFF
466
- e.local_file_header_offset = read_8b(zip64_extra) if e.local_file_header_offset == 0xFFFFFFFF
467
- # Disk number comes last and we can skip it anyway, since we do not support multi-disk archives
525
+ # Rubocop: convention: Line is too long.
526
+ if e.uncompressed_size == 0xFFFFFFFF
527
+ e.uncompressed_size = read_8b(zip64_extra)
528
+ end
529
+ if e.compressed_size == 0xFFFFFFFF
530
+ e.compressed_size = read_8b(zip64_extra)
531
+ end
532
+ if e.local_file_header_offset == 0xFFFFFFFF
533
+ e.local_file_header_offset = read_8b(zip64_extra)
534
+ end
535
+ # Disk number comes last and we can skip it anyway, since we do
536
+ # not support multi-disk archives
468
537
  end
469
538
  end
470
539
  end
@@ -480,25 +549,30 @@ class ZipTricks::FileReader
480
549
  file_io.seek(implied_position_of_eocd_record, IO::SEEK_SET)
481
550
  str_containing_eocd_record = file_io.read(MAX_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE)
482
551
  eocd_idx_in_buf = locate_eocd_signature(str_containing_eocd_record)
483
-
552
+
484
553
  raise MissingEOCD unless eocd_idx_in_buf
485
-
554
+
486
555
  eocd_offset = implied_position_of_eocd_record + eocd_idx_in_buf
487
- log { 'Found EOCD signature at offset %d' % eocd_offset }
488
-
556
+ log { format('Found EOCD signature at offset %d', eocd_offset) }
557
+
489
558
  eocd_offset
490
559
  end
491
560
 
492
- # This is tricky. Essentially, we have to scan the maximum possible number of bytes (that the EOCD can
493
- # theoretically occupy including the comment), and we have to find a combination of:
494
- # [EOCD signature, <some ZIP medatata>, comment byte size, the comment of that size, eof].
495
- # The only way I could find to do this was with a sliding window, but there probably is a better way.
561
+ # This is tricky. Essentially, we have to scan the maximum possible number
562
+ # of bytes (that the EOCD can theoretically occupy including the comment),
563
+ # and we have to find a combination of:
564
+ # [EOCD signature, <some ZIP medatata>, comment byte size, the comment of
565
+ # that size, eof].
566
+ # The only way I could find to do this was with a sliding window, but
567
+ # there probably is a better way.
568
+ # Rubocop: convention: Assignment Branch Condition size for
569
+ # locate_eocd_signature is too high. [17.49/15]
570
+ # Rubocop: convention: Method has too many lines. [14/10]
496
571
  def locate_eocd_signature(in_str)
497
572
  # We have to scan from the _very_ tail. We read the very minimum size
498
573
  # the EOCD record can have (up to and including the comment size), using
499
574
  # a sliding window. Once our end offset matches the comment size we found our
500
575
  # EOCD marker.
501
- eocd_signature_int = 0x06054b50
502
576
  unpack_pattern = 'VvvvvVVv'
503
577
  minimum_record_size = 22
504
578
  end_location = minimum_record_size * -1
@@ -507,23 +581,26 @@ class ZipTricks::FileReader
507
581
  # We use negative values because if we used positive slice indices
508
582
  # we would have to detect the rollover ourselves
509
583
  break unless window = in_str[end_location, minimum_record_size]
510
-
584
+
511
585
  window_location = in_str.bytesize + end_location
512
586
  unpacked = window.unpack(unpack_pattern)
513
587
  # If we found the signarue, pick up the comment size, and check if the size of the window
514
588
  # plus that comment size is where we are in the string. If we are - bingo.
515
- if unpacked[0] == 0x06054b50 && comment_size = unpacked[-1]
589
+ if unpacked[0] == 0x06054b50 && comment_size = unpacked[-1]
516
590
  assumed_eocd_location = in_str.bytesize - comment_size - minimum_record_size
517
591
  # if the comment size is where we should be at - we found our EOCD
518
592
  return assumed_eocd_location if assumed_eocd_location == window_location
519
593
  end
520
-
594
+
521
595
  end_location -= 1 # Shift the window back, by one byte, and try again.
522
596
  end
523
597
  end
524
-
598
+
525
599
  # Find the Zip64 EOCD locator segment offset. Do this by seeking backwards from the
526
600
  # EOCD record in the archive by fixed offsets
601
+ # Rubocop: convention: Assignment Branch Condition size for
602
+ # get_zip64_eocd_location is too high. [15.17/15]
603
+ # Rubocop: convention: Method has too many lines. [15/10]
527
604
  def get_zip64_eocd_location(file_io, eocd_offset)
528
605
  zip64_eocd_loc_offset = eocd_offset
529
606
  zip64_eocd_loc_offset -= 4 # The signature
@@ -531,28 +608,34 @@ class ZipTricks::FileReader
531
608
  zip64_eocd_loc_offset -= 8 # Offset of the zip64 central directory record
532
609
  zip64_eocd_loc_offset -= 4 # Total number of disks
533
610
 
534
- log { 'Will look for the Zip64 EOCD locator signature at offset %d' % zip64_eocd_loc_offset }
611
+ log do
612
+ format('Will look for the Zip64 EOCD locator signature at offset %d',
613
+ zip64_eocd_loc_offset)
614
+ end
535
615
 
536
616
  # If the offset is negative there is certainly no Zip64 EOCD locator here
537
617
  return unless zip64_eocd_loc_offset >= 0
538
618
 
539
619
  file_io.seek(zip64_eocd_loc_offset, IO::SEEK_SET)
540
620
  assert_signature(file_io, 0x07064b50)
541
-
542
- log { 'Found Zip64 EOCD locator at offset %d' % zip64_eocd_loc_offset }
621
+
622
+ log { format('Found Zip64 EOCD locator at offset %d', zip64_eocd_loc_offset) }
543
623
 
544
624
  disk_num = read_4b(file_io) # number of the disk
545
- raise UnsupportedFeature, "The archive spans multiple disks" if disk_num != 0
625
+ raise UnsupportedFeature, 'The archive spans multiple disks' if disk_num != 0
546
626
  read_8b(file_io)
547
627
  rescue ReadError
548
628
  nil
549
629
  end
550
630
 
631
+ # Rubocop: convention: Assignment Branch Condition size for
632
+ # num_files_and_central_directory_offset_zip64 is too high. [21.12/15]
633
+ # Rubocop: convention: Method has too many lines. [17/10]
551
634
  def num_files_and_central_directory_offset_zip64(io, zip64_end_of_cdir_location)
552
635
  seek(io, zip64_end_of_cdir_location)
553
-
636
+
554
637
  assert_signature(io, 0x06064b50)
555
-
638
+
556
639
  zip64_eocdr_size = read_8b(io)
557
640
  zip64_eocdr = read_n(io, zip64_eocdr_size) # Reading in bulk is cheaper
558
641
  zip64_eocdr = StringIO.new(zip64_eocdr)
@@ -561,13 +644,21 @@ class ZipTricks::FileReader
561
644
 
562
645
  disk_n = read_4b(zip64_eocdr) # number of this disk
563
646
  disk_n_with_eocdr = read_4b(zip64_eocdr) # number of the disk with the EOCDR
564
- raise UnsupportedFeature, "The archive spans multiple disks" if disk_n != disk_n_with_eocdr
647
+ if disk_n != disk_n_with_eocdr
648
+ raise UnsupportedFeature, 'The archive spans multiple disks'
649
+ end
565
650
 
566
651
  num_files_this_disk = read_8b(zip64_eocdr) # number of files on this disk
567
652
  num_files_total = read_8b(zip64_eocdr) # files total in the central directory
568
- raise UnsupportedFeature, "The archive spans multiple disks" if num_files_this_disk != num_files_total
569
653
 
570
- log { 'Zip64 EOCD record states there are %d files in the archive' % num_files_total }
654
+ if num_files_this_disk != num_files_total
655
+ raise UnsupportedFeature, 'The archive spans multiple disks'
656
+ end
657
+
658
+ log do
659
+ format('Zip64 EOCD record states there are %d files in the archive',
660
+ num_files_total)
661
+ end
571
662
 
572
663
  central_dir_size = read_8b(zip64_eocdr) # Size of the central directory
573
664
  central_dir_offset = read_8b(zip64_eocdr) # Where the central directory starts
@@ -575,58 +666,63 @@ class ZipTricks::FileReader
575
666
  [num_files_total, central_dir_offset, central_dir_size]
576
667
  end
577
668
 
578
- C_V = 'V'.freeze
579
- C_v = 'v'.freeze
580
- C_Qe = 'Q<'.freeze
581
-
582
- # To prevent too many tiny reads, read the maximum possible size of end of central directory record
583
- # upfront (all the fixed fields + at most 0xFFFF bytes of the archive comment)
584
- MAX_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE = begin
585
- 4 + # Offset of the start of central directory
586
- 4 + # Size of the central directory
587
- 2 + # Number of files in the cdir
588
- 4 + # End-of-central-directory signature
589
- 2 + # Number of this disk
590
- 2 + # Number of disk with the start of cdir
591
- 2 + # Number of files in the cdir of this disk
592
- 2 + # The comment size
593
- 0xFFFF # Maximum comment size
594
- end
669
+ C_V = 'V'
670
+ C_v = 'v'
671
+ C_Qe = 'Q<'
672
+
673
+ # To prevent too many tiny reads, read the maximum possible size of end of
674
+ # central directory record upfront (all the fixed fields + at most 0xFFFF
675
+ # bytes of the archive comment)
676
+ MAX_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE =
677
+ begin
678
+ 4 + # Offset of the start of central directory
679
+ 4 + # Size of the central directory
680
+ 2 + # Number of files in the cdir
681
+ 4 + # End-of-central-directory signature
682
+ 2 + # Number of this disk
683
+ 2 + # Number of disk with the start of cdir
684
+ 2 + # Number of files in the cdir of this disk
685
+ 2 + # The comment size
686
+ 0xFFFF # Maximum comment size
687
+ end
595
688
 
596
689
  # To prevent too many tiny reads, read the maximum possible size of the local file header upfront.
597
690
  # The maximum size is all the usual items, plus the maximum size
598
691
  # of the filename (0xFFFF bytes) and the maximum size of the extras (0xFFFF bytes)
599
- MAX_LOCAL_HEADER_SIZE = begin
600
- 4 + # signature
601
- 2 + # Version needed to extract
602
- 2 + # gp flags
603
- 2 + # storage mode
604
- 2 + # dos time
605
- 2 + # dos date
606
- 4 + # CRC32
607
- 4 + # Comp size
608
- 4 + # Uncomp size
609
- 2 + # Filename size
610
- 2 + # Extra fields size
611
- 0xFFFF + # Maximum filename size
612
- 0xFFFF # Maximum extra fields size
613
- end
692
+ MAX_LOCAL_HEADER_SIZE =
693
+ begin
694
+ 4 + # signature
695
+ 2 + # Version needed to extract
696
+ 2 + # gp flags
697
+ 2 + # storage mode
698
+ 2 + # dos time
699
+ 2 + # dos date
700
+ 4 + # CRC32
701
+ 4 + # Comp size
702
+ 4 + # Uncomp size
703
+ 2 + # Filename size
704
+ 2 + # Extra fields size
705
+ 0xFFFF + # Maximum filename size
706
+ 0xFFFF # Maximum extra fields size
707
+ end
614
708
 
615
- SIZE_OF_USABLE_EOCD_RECORD = begin
616
- 4 + # Signature
617
- 2 + # Number of this disk
618
- 2 + # Number of the disk with the EOCD record
619
- 2 + # Number of entries in the central directory of this disk
620
- 2 + # Number of entries in the central directory total
621
- 4 + # Size of the central directory
622
- 4 # Start of the central directory offset
623
- end
709
+ SIZE_OF_USABLE_EOCD_RECORD =
710
+ begin
711
+ 4 + # Signature
712
+ 2 + # Number of this disk
713
+ 2 + # Number of the disk with the EOCD record
714
+ 2 + # Number of entries in the central directory of this disk
715
+ 2 + # Number of entries in the central directory total
716
+ 4 + # Size of the central directory
717
+ 4 # Start of the central directory offset
718
+ end
624
719
 
720
+ # Rubocop: convention: Method has too many lines. [11/10]
625
721
  def num_files_and_central_directory_offset(file_io, eocd_offset)
626
722
  seek(file_io, eocd_offset)
627
723
 
628
724
  # The size of the EOCD record is known upfront, so use a strict read
629
- eocd_record_str = read_n(file_io, SIZE_OF_USABLE_EOCD_RECORD)
725
+ eocd_record_str = read_n(file_io, SIZE_OF_USABLE_EOCD_RECORD)
630
726
  io = StringIO.new(eocd_record_str)
631
727
 
632
728
  assert_signature(io, 0x06054b50)
@@ -640,8 +736,8 @@ class ZipTricks::FileReader
640
736
  end
641
737
 
642
738
  private_constant :C_V, :C_v, :C_Qe, :MAX_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE,
643
- :MAX_LOCAL_HEADER_SIZE, :SIZE_OF_USABLE_EOCD_RECORD
644
-
739
+ :MAX_LOCAL_HEADER_SIZE, :SIZE_OF_USABLE_EOCD_RECORD
740
+
645
741
  # Is provided as a stub to be overridden in a subclass if you need it. Will report
646
742
  # during various stages of reading. The log message is contained in the return value
647
743
  # of `yield` in the method (the log messages are lazy-evaluated).
@@ -649,11 +745,11 @@ class ZipTricks::FileReader
649
745
  # The most minimal implementation for the method is just this:
650
746
  # $stderr.puts(yield)
651
747
  end
652
-
748
+
653
749
  def parse_out_extra_fields(extra_fields_str)
654
750
  extra_table = {}
655
751
  extras_buf = StringIO.new(extra_fields_str)
656
- until extras_buf.eof? do
752
+ until extras_buf.eof?
657
753
  extra_id = read_2b(extras_buf)
658
754
  extra_size = read_2b(extras_buf)
659
755
  extra_contents = read_n(extras_buf, extra_size)