zip_tricks 3.1.0 → 3.1.1

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