zip_tricks 3.1.0 → 3.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d3b168fde79da25b24e3cb4b69caac818f1337f3
4
- data.tar.gz: e1d7b9995636811c9c662a70e2caffb3a085709b
3
+ metadata.gz: 3434ce881bfe7fdf3494ed494f8e251082d94510
4
+ data.tar.gz: 30062193918dacf4a018d7bde923303f0a14c47f
5
5
  SHA512:
6
- metadata.gz: 65f27dacbcc5fd09e229f8543497fe3b3ff3da861696b355e01688f8b78dc7d02f4163de11e4a909c83f273a42005f63be7c1c7b7196f8657f0dd4d26cc4cc4f
7
- data.tar.gz: 66b7db54cdc4702d0f867b8f263ea1bca2016405dd19db43bc4a7368ddb0d4b912b67344b7d0efc3156b0fbfe5868aa78b3cc6b8c22c87cddfd57282c8842dec
6
+ metadata.gz: 88be7f1bebd8e0faa8906203b594e022b173390039732a3eaafa53ec903770b0a619f7a0895a8203b75c6c09f5908ca400d86611f443e34fdead53554edd8e14
7
+ data.tar.gz: 5c4362394b3666ce528e16c657b019a8185875555af2202a8c976df523c309017ac060ec61082617b3573722cb1d2e4eb002502ef9c8b770227859756e0afe5f
@@ -188,26 +188,36 @@ class ZipTricks::FileReader
188
188
  # Parse an IO handle to a ZIP archive into an array of Entry objects.
189
189
  #
190
190
  # @param io[#tell, #seek, #read, #size] an IO-ish object
191
+ # @param read_local_headers[Boolean] whether to proceed to read the local headers in addition to the central directory
191
192
  # @return [Array<Entry>] an array of entries within the ZIP being parsed
192
- def read_zip_structure(io)
193
+ def read_zip_structure(io, read_local_headers: true)
193
194
  zip_file_size = io.size
194
195
  eocd_offset = get_eocd_offset(io, zip_file_size)
195
196
 
196
- zip64_end_of_cdir_location = get_zip64_eocd_locator_offset(io, eocd_offset)
197
+ zip64_end_of_cdir_location = get_zip64_eocd_location(io, eocd_offset)
197
198
  num_files, cdir_location, cdir_size = if zip64_end_of_cdir_location
198
199
  num_files_and_central_directory_offset_zip64(io, zip64_end_of_cdir_location)
199
200
  else
200
201
  num_files_and_central_directory_offset(io, eocd_offset)
201
202
  end
203
+ log { 'Located the central directory start at %d' % cdir_location }
202
204
  seek(io, cdir_location)
203
205
 
204
206
  # Read the entire central directory in one fell swoop
205
207
  central_directory_str = read_n(io, cdir_size)
206
208
  central_directory_io = StringIO.new(central_directory_str)
209
+ log { 'Read %d bytes with central directory entries' % cdir_size }
207
210
 
208
- entries = (1..num_files).map { read_cdir_entry(central_directory_io) }
209
- entries.each do |entry|
210
- entry.compressed_data_offset = find_compressed_data_start_offset(io, entry.local_file_header_offset)
211
+ entries = (0...num_files).map do |entry_n|
212
+ log { 'Reading the central directory entry %d starting at offset %d' % [entry_n, cdir_location + central_directory_io.tell] }
213
+ read_cdir_entry(central_directory_io)
214
+ end
215
+
216
+ entries.each_with_index do |entry, i|
217
+ if read_local_headers
218
+ log { 'Reading the local header for entry %d at offset %d' % [i, entry.local_file_header_offset] }
219
+ entry.compressed_data_offset = find_compressed_data_start_offset(io, entry.local_file_header_offset)
220
+ end
211
221
  end
212
222
  end
213
223
 
@@ -349,9 +359,20 @@ class ZipTricks::FileReader
349
359
  # ...of which we really only need the Zip64 extra
350
360
  if zip64_extra_contents = extra_table[1] # Zip64 extra
351
361
  zip64_extra = StringIO.new(zip64_extra_contents)
352
- e.uncompressed_size = read_8b(zip64_extra)
353
- e.compressed_size = read_8b(zip64_extra)
354
- e.local_file_header_offset = read_8b(zip64_extra)
362
+ log { 'Will read Zip64 extra data for %s, %d bytes' % [e.filename, zip64_extra.size] }
363
+ # Now here be dragons. The APPNOTE specifies that
364
+ #
365
+ # > The order of the fields in the ZIP64 extended
366
+ # > information record is fixed, but the fields will
367
+ # > only appear if the corresponding Local or Central
368
+ # > directory record field is set to 0xFFFF or 0xFFFFFFFF.
369
+ #
370
+ # It means that before we read this stuff we need to check if the previously-read
371
+ # values are at overflow, and only _then_ proceed to read them. Bah.
372
+ e.uncompressed_size = read_8b(zip64_extra) if e.uncompressed_size == 0xFFFFFFFF
373
+ e.compressed_size = read_8b(zip64_extra) if e.compressed_size == 0xFFFFFFFF
374
+ e.local_file_header_offset = read_8b(zip64_extra) if e.local_file_header_offset == 0xFFFFFFFF
375
+ # Disk number comes last and we can skip it anyway, since we do not support multi-disk archives
355
376
  end
356
377
  end
357
378
  end
@@ -369,8 +390,11 @@ class ZipTricks::FileReader
369
390
  eocd_idx_in_buf = locate_eocd_signature(str_containing_eocd_record)
370
391
 
371
392
  raise "Could not find the EOCD signature in the buffer - maybe a malformed ZIP file" unless eocd_idx_in_buf
372
-
373
- implied_position_of_eocd_record + eocd_idx_in_buf
393
+
394
+ eocd_offset = implied_position_of_eocd_record + eocd_idx_in_buf
395
+ log { 'Found EOCD signature at offset %d' % eocd_offset }
396
+
397
+ eocd_offset
374
398
  end
375
399
 
376
400
  # This is tricky. Essentially, we have to scan the maximum possible number of bytes (that the EOCD can
@@ -394,7 +418,6 @@ class ZipTricks::FileReader
394
418
 
395
419
  window_location = in_str.bytesize + end_location
396
420
  unpacked = window.unpack(unpack_pattern)
397
-
398
421
  # If we found the signarue, pick up the comment size, and check if the size of the window
399
422
  # plus that comment size is where we are in the string. If we are - bingo.
400
423
  if unpacked[0] == 0x06054b50 && comment_size = unpacked[-1]
@@ -409,18 +432,23 @@ class ZipTricks::FileReader
409
432
 
410
433
  # Find the Zip64 EOCD locator segment offset. Do this by seeking backwards from the
411
434
  # EOCD record in the archive by fixed offsets
412
- def get_zip64_eocd_locator_offset(file_io, eocd_offset)
435
+ def get_zip64_eocd_location(file_io, eocd_offset)
413
436
  zip64_eocd_loc_offset = eocd_offset
414
437
  zip64_eocd_loc_offset -= 4 # The signature
415
438
  zip64_eocd_loc_offset -= 4 # Which disk has the Zip64 end of central directory record
416
439
  zip64_eocd_loc_offset -= 8 # Offset of the zip64 central directory record
417
440
  zip64_eocd_loc_offset -= 4 # Total number of disks
418
441
 
442
+ log { 'Will look for the Zip64 EOCD locator signature at offset %d' % zip64_eocd_loc_offset }
443
+
419
444
  # If the offset is negative there is certainly no Zip64 EOCD locator here
420
445
  return unless zip64_eocd_loc_offset >= 0
421
446
 
422
447
  file_io.seek(zip64_eocd_loc_offset, IO::SEEK_SET)
423
448
  assert_signature(file_io, 0x07064b50)
449
+
450
+ log { 'Found Zip64 EOCD locator at offset %d' % zip64_eocd_loc_offset }
451
+
424
452
  disk_num = read_4b(file_io) # number of the disk
425
453
  raise UnsupportedFeature, "The archive spans multiple disks" if disk_num != 0
426
454
  read_8b(file_io)
@@ -445,9 +473,10 @@ class ZipTricks::FileReader
445
473
 
446
474
  num_files_this_disk = read_8b(zip64_eocdr) # number of files on this disk
447
475
  num_files_total = read_8b(zip64_eocdr) # files total in the central directory
448
-
449
476
  raise UnsupportedFeature, "The archive spans multiple disks" if num_files_this_disk != num_files_total
450
477
 
478
+ log { 'Zip64 EOCD record states there are %d files in the archive' % num_files_total }
479
+
451
480
  central_dir_size = read_8b(zip64_eocdr) # Size of the central directory
452
481
  central_dir_offset = read_8b(zip64_eocdr) # Where the central directory starts
453
482
 
@@ -519,4 +548,12 @@ class ZipTricks::FileReader
519
548
 
520
549
  private_constant :C_V, :C_v, :C_Qe, :MAX_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE,
521
550
  :MAX_LOCAL_HEADER_SIZE, :SIZE_OF_USABLE_EOCD_RECORD
551
+
552
+ # Is provided as a stub to be overridden in a subclass if you need it. Will report
553
+ # during various stages of reading. The log message is contained in the return value
554
+ # of `yield` in the method (the log messages are lazy-evaluated).
555
+ def log
556
+ # The most minimal implementation for the method is just this:
557
+ # $stderr.puts(yield)
558
+ end
522
559
  end
data/lib/zip_tricks.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module ZipTricks
2
- VERSION = '3.1.0'
2
+ VERSION = '3.1.1'
3
3
 
4
4
  # Require all the sub-components except myself
5
5
  Dir.glob(__dir__ + '/**/*.rb').sort.each {|p| require p unless p == __FILE__ }
@@ -66,4 +66,17 @@ describe ZipTricks::FileReader do
66
66
  entries = described_class.read_zip_structure(z)
67
67
  expect(entries.length).to eq(1)
68
68
  end
69
+
70
+ it 'can handle a Zip64 central directory fields that only contains the required fields (substitutes for standard fields)' do
71
+ # In this example central directory, 2 entries contain Zip64 extra where only the local header offset is set (8 bytes each)
72
+ # This is the exceptional case where we have to poke at a private method directly
73
+ File.open(__dir__ + '/cdir_entry_with_partial_use_of_zip64_extra_fields.bin', 'rb') do |f|
74
+ reader = described_class.new
75
+ entry = reader.send(:read_cdir_entry, f)
76
+ expect(entry.local_file_header_offset).to eq(4312401349)
77
+ expect(entry.filename).to eq('Motorhead - Ace Of Spades.srt')
78
+ expect(entry.compressed_size).to eq(69121)
79
+ expect(entry.uncompressed_size).to eq(69121)
80
+ end
81
+ end
69
82
  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 3.1.0 ruby lib
5
+ # stub: zip_tricks 3.1.1 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "zip_tricks"
9
- s.version = "3.1.0"
9
+ s.version = "3.1.1"
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-08-16"
14
+ s.date = "2016-08-17"
15
15
  s.description = "Makes rubyzip stream, for real"
16
16
  s.email = "me@julik.nl"
17
17
  s.extra_rdoc_files = [
@@ -52,6 +52,7 @@ Gem::Specification.new do |s|
52
52
  "spec/spec_helper.rb",
53
53
  "spec/zip_tricks/block_deflate_spec.rb",
54
54
  "spec/zip_tricks/block_write_spec.rb",
55
+ "spec/zip_tricks/cdir_entry_with_partial_use_of_zip64_extra_fields.bin",
55
56
  "spec/zip_tricks/file_reader_spec.rb",
56
57
  "spec/zip_tricks/rack_body_spec.rb",
57
58
  "spec/zip_tricks/remote_io_spec.rb",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zip_tricks
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-16 00:00:00.000000000 Z
11
+ date: 2016-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
@@ -203,6 +203,7 @@ files:
203
203
  - spec/spec_helper.rb
204
204
  - spec/zip_tricks/block_deflate_spec.rb
205
205
  - spec/zip_tricks/block_write_spec.rb
206
+ - spec/zip_tricks/cdir_entry_with_partial_use_of_zip64_extra_fields.bin
206
207
  - spec/zip_tricks/file_reader_spec.rb
207
208
  - spec/zip_tricks/rack_body_spec.rb
208
209
  - spec/zip_tricks/remote_io_spec.rb