rubyzip 1.2.2 → 2.3.0

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.
Files changed (114) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +64 -23
  3. data/Rakefile +3 -0
  4. data/lib/zip.rb +6 -3
  5. data/lib/zip/central_directory.rb +9 -5
  6. data/lib/zip/constants.rb +52 -0
  7. data/lib/zip/crypto/decrypted_io.rb +40 -0
  8. data/lib/zip/crypto/traditional_encryption.rb +9 -9
  9. data/lib/zip/decompressor.rb +19 -1
  10. data/lib/zip/dos_time.rb +12 -7
  11. data/lib/zip/entry.rb +69 -37
  12. data/lib/zip/entry_set.rb +2 -0
  13. data/lib/zip/errors.rb +2 -0
  14. data/lib/zip/extra_field.rb +11 -9
  15. data/lib/zip/extra_field/generic.rb +10 -9
  16. data/lib/zip/extra_field/ntfs.rb +4 -0
  17. data/lib/zip/extra_field/old_unix.rb +3 -1
  18. data/lib/zip/extra_field/universal_time.rb +42 -12
  19. data/lib/zip/extra_field/unix.rb +3 -1
  20. data/lib/zip/extra_field/zip64.rb +4 -2
  21. data/lib/zip/file.rb +115 -70
  22. data/lib/zip/filesystem.rb +193 -177
  23. data/lib/zip/inflater.rb +24 -36
  24. data/lib/zip/input_stream.rb +33 -26
  25. data/lib/zip/ioextras.rb +1 -1
  26. data/lib/zip/ioextras/abstract_input_stream.rb +19 -8
  27. data/lib/zip/ioextras/abstract_output_stream.rb +1 -1
  28. data/lib/zip/null_decompressor.rb +1 -9
  29. data/lib/zip/output_stream.rb +14 -5
  30. data/lib/zip/pass_thru_compressor.rb +2 -2
  31. data/lib/zip/pass_thru_decompressor.rb +13 -22
  32. data/lib/zip/streamable_directory.rb +3 -3
  33. data/lib/zip/streamable_stream.rb +6 -10
  34. data/lib/zip/version.rb +1 -1
  35. data/samples/example.rb +2 -2
  36. data/samples/example_filesystem.rb +1 -1
  37. data/samples/gtk_ruby_zip.rb +19 -19
  38. data/samples/qtzip.rb +6 -6
  39. data/samples/write_simple.rb +2 -4
  40. data/samples/zipfind.rb +23 -22
  41. metadata +32 -167
  42. data/test/basic_zip_file_test.rb +0 -60
  43. data/test/case_sensitivity_test.rb +0 -69
  44. data/test/central_directory_entry_test.rb +0 -69
  45. data/test/central_directory_test.rb +0 -100
  46. data/test/crypto/null_encryption_test.rb +0 -57
  47. data/test/crypto/traditional_encryption_test.rb +0 -80
  48. data/test/data/WarnInvalidDate.zip +0 -0
  49. data/test/data/file1.txt +0 -46
  50. data/test/data/file1.txt.deflatedData +0 -0
  51. data/test/data/file2.txt +0 -1504
  52. data/test/data/globTest.zip +0 -0
  53. data/test/data/globTest/foo.txt +0 -0
  54. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  55. data/test/data/globTest/food.txt +0 -0
  56. data/test/data/gpbit3stored.zip +0 -0
  57. data/test/data/mimetype +0 -1
  58. data/test/data/notzippedruby.rb +0 -7
  59. data/test/data/ntfs.zip +0 -0
  60. data/test/data/oddExtraField.zip +0 -0
  61. data/test/data/path_traversal/Makefile +0 -10
  62. data/test/data/path_traversal/jwilk/README.md +0 -5
  63. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  64. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  65. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  66. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  67. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  68. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  69. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  70. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  71. data/test/data/path_traversal/relative1.zip +0 -0
  72. data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
  73. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  74. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  75. data/test/data/rubycode.zip +0 -0
  76. data/test/data/rubycode2.zip +0 -0
  77. data/test/data/test.xls +0 -0
  78. data/test/data/testDirectory.bin +0 -0
  79. data/test/data/zip64-sample.zip +0 -0
  80. data/test/data/zipWithDirs.zip +0 -0
  81. data/test/data/zipWithEncryption.zip +0 -0
  82. data/test/deflater_test.rb +0 -65
  83. data/test/encryption_test.rb +0 -42
  84. data/test/entry_set_test.rb +0 -163
  85. data/test/entry_test.rb +0 -154
  86. data/test/errors_test.rb +0 -35
  87. data/test/extra_field_test.rb +0 -76
  88. data/test/file_extract_directory_test.rb +0 -54
  89. data/test/file_extract_test.rb +0 -83
  90. data/test/file_permissions_test.rb +0 -65
  91. data/test/file_split_test.rb +0 -57
  92. data/test/file_test.rb +0 -601
  93. data/test/filesystem/dir_iterator_test.rb +0 -58
  94. data/test/filesystem/directory_test.rb +0 -139
  95. data/test/filesystem/file_mutating_test.rb +0 -87
  96. data/test/filesystem/file_nonmutating_test.rb +0 -508
  97. data/test/filesystem/file_stat_test.rb +0 -64
  98. data/test/gentestfiles.rb +0 -126
  99. data/test/inflater_test.rb +0 -14
  100. data/test/input_stream_test.rb +0 -182
  101. data/test/ioextras/abstract_input_stream_test.rb +0 -102
  102. data/test/ioextras/abstract_output_stream_test.rb +0 -106
  103. data/test/ioextras/fake_io_test.rb +0 -18
  104. data/test/local_entry_test.rb +0 -154
  105. data/test/output_stream_test.rb +0 -128
  106. data/test/pass_thru_compressor_test.rb +0 -30
  107. data/test/pass_thru_decompressor_test.rb +0 -14
  108. data/test/path_traversal_test.rb +0 -134
  109. data/test/samples/example_recursive_test.rb +0 -37
  110. data/test/settings_test.rb +0 -95
  111. data/test/test_helper.rb +0 -234
  112. data/test/unicode_file_names_and_comments_test.rb +0 -62
  113. data/test/zip64_full_test.rb +0 -51
  114. data/test/zip64_support_test.rb +0 -14
@@ -1,9 +1,27 @@
1
1
  module Zip
2
2
  class Decompressor #:nodoc:all
3
3
  CHUNK_SIZE = 32_768
4
- def initialize(input_stream)
4
+
5
+ def self.decompressor_classes
6
+ @decompressor_classes ||= {}
7
+ end
8
+
9
+ def self.register(compression_method, decompressor_class)
10
+ decompressor_classes[compression_method] = decompressor_class
11
+ end
12
+
13
+ def self.find_by_compression_method(compression_method)
14
+ decompressor_classes[compression_method]
15
+ end
16
+
17
+ attr_reader :input_stream
18
+ attr_reader :decompressed_size
19
+
20
+ def initialize(input_stream, decompressed_size = nil)
5
21
  super()
22
+
6
23
  @input_stream = input_stream
24
+ @decompressed_size = decompressed_size
7
25
  end
8
26
  end
9
27
  end
@@ -29,13 +29,18 @@ module Zip
29
29
  to_i / 2 == other.to_i / 2
30
30
  end
31
31
 
32
- def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
33
- second = 2 * (0b11111 & binaryDosTime)
34
- minute = (0b11111100000 & binaryDosTime) >> 5
35
- hour = (0b1111100000000000 & binaryDosTime) >> 11
36
- day = (0b11111 & binaryDosDate)
37
- month = (0b111100000 & binaryDosDate) >> 5
38
- year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
32
+ # Create a DOSTime instance from a vanilla Time instance.
33
+ def self.from_time(time)
34
+ local(time.year, time.month, time.day, time.hour, time.min, time.sec)
35
+ end
36
+
37
+ def self.parse_binary_dos_format(bin_dos_date, bin_dos_time)
38
+ second = 2 * (0b11111 & bin_dos_time)
39
+ minute = (0b11111100000 & bin_dos_time) >> 5
40
+ hour = (0b1111100000000000 & bin_dos_time) >> 11
41
+ day = (0b11111 & bin_dos_date)
42
+ month = (0b111100000 & bin_dos_date) >> 5
43
+ year = ((0b1111111000000000 & bin_dos_date) >> 9) + 1980
39
44
  begin
40
45
  local(year, month, day, hour, minute, second)
41
46
  end
@@ -1,3 +1,4 @@
1
+ require 'pathname'
1
2
  module Zip
2
3
  class Entry
3
4
  STORED = 0
@@ -33,7 +34,7 @@ module Zip
33
34
  end
34
35
  @follow_symlinks = false
35
36
 
36
- @restore_times = true
37
+ @restore_times = false
37
38
  @restore_permissions = false
38
39
  @restore_ownership = false
39
40
  # BUG: need an extra field to support uid/gid's
@@ -47,6 +48,7 @@ module Zip
47
48
 
48
49
  def check_name(name)
49
50
  return unless name.start_with?('/')
51
+
50
52
  raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
51
53
  end
52
54
 
@@ -68,7 +70,15 @@ module Zip
68
70
  @time = args[8] || ::Zip::DOSTime.now
69
71
 
70
72
  @ftype = name_is_directory? ? :directory : :file
71
- @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
73
+ @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField)
74
+ end
75
+
76
+ def encrypted?
77
+ gp_flags & 1 == 1
78
+ end
79
+
80
+ def incomplete?
81
+ gp_flags & 8 == 8
72
82
  end
73
83
 
74
84
  def time
@@ -90,11 +100,12 @@ module Zip
90
100
  @extra.create('UniversalTime')
91
101
  end
92
102
  (@extra['UniversalTime'] || @extra['NTFS']).mtime = value
93
- @time = value
103
+ @time = value
94
104
  end
95
105
 
96
106
  def file_type_is?(type)
97
107
  raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
108
+
98
109
  @ftype == type
99
110
  end
100
111
 
@@ -115,9 +126,10 @@ module Zip
115
126
  def name_safe?
116
127
  cleanpath = Pathname.new(@name).cleanpath
117
128
  return false unless cleanpath.relative?
129
+
118
130
  root = ::File::SEPARATOR
119
131
  naive_expanded_path = ::File.join(root, cleanpath.to_s)
120
- cleanpath.expand_path(root).to_s == naive_expanded_path
132
+ ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
121
133
  end
122
134
 
123
135
  def local_entry_offset #:nodoc:all
@@ -144,6 +156,7 @@ module Zip
144
156
  # that we didn't change the header size (and thus clobber file data or something)
145
157
  def verify_local_header_size!
146
158
  return if @local_header_size.nil?
159
+
147
160
  new_size = calculate_local_header_size
148
161
  raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
149
162
  end
@@ -162,19 +175,16 @@ module Zip
162
175
  # is passed.
163
176
  def extract(dest_path = nil, &block)
164
177
  if dest_path.nil? && !name_safe?
165
- puts "WARNING: skipped #{@name} as unsafe"
178
+ warn "WARNING: skipped '#{@name}' as unsafe."
166
179
  return self
167
180
  end
168
181
 
169
182
  dest_path ||= @name
170
183
  block ||= proc { ::Zip.on_exists_proc }
171
184
 
172
- if directory? || file? || symlink?
173
- __send__("create_#{@ftype}", dest_path, &block)
174
- else
175
- raise "unknown file type #{inspect}"
176
- end
185
+ raise "unknown file type #{inspect}" unless directory? || file? || symlink?
177
186
 
187
+ __send__("create_#{@ftype}", dest_path, &block)
178
188
  self
179
189
  end
180
190
 
@@ -184,15 +194,15 @@ module Zip
184
194
 
185
195
  class << self
186
196
  def read_zip_short(io) # :nodoc:
187
- io.read(2).unpack('v')[0]
197
+ io.read(2).unpack1('v')
188
198
  end
189
199
 
190
200
  def read_zip_long(io) # :nodoc:
191
- io.read(4).unpack('V')[0]
201
+ io.read(4).unpack1('V')
192
202
  end
193
203
 
194
204
  def read_zip_64_long(io) # :nodoc:
195
- io.read(8).unpack('Q<')[0]
205
+ io.read(8).unpack1('Q<')
196
206
  end
197
207
 
198
208
  def read_c_dir_entry(io) #:nodoc:all
@@ -217,8 +227,6 @@ module Zip
217
227
  end
218
228
  end
219
229
 
220
- public
221
-
222
230
  def unpack_local_entry(buf)
223
231
  @header_signature,
224
232
  @version,
@@ -248,6 +256,7 @@ module Zip
248
256
  unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE
249
257
  raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'"
250
258
  end
259
+
251
260
  set_time(@last_mod_date, @last_mod_time)
252
261
 
253
262
  @name = io.read(@name_length)
@@ -260,13 +269,14 @@ module Zip
260
269
 
261
270
  if extra && extra.bytesize != @extra_length
262
271
  raise ::Zip::Error, 'Truncated local zip entry header'
272
+ end
273
+
274
+ if @extra.kind_of?(::Zip::ExtraField)
275
+ @extra.merge(extra) if extra
263
276
  else
264
- if @extra.is_a?(::Zip::ExtraField)
265
- @extra.merge(extra) if extra
266
- else
267
- @extra = ::Zip::ExtraField.new(extra)
268
- end
277
+ @extra = ::Zip::ExtraField.new(extra)
269
278
  end
279
+
270
280
  parse_zip64_extra(true)
271
281
  @local_header_size = calculate_local_header_size
272
282
  end
@@ -275,10 +285,10 @@ module Zip
275
285
  zip64 = @extra['Zip64']
276
286
  [::Zip::LOCAL_ENTRY_SIGNATURE,
277
287
  @version_needed_to_extract, # version needed to extract
278
- @gp_flags, # @gp_flags ,
288
+ @gp_flags, # @gp_flags
279
289
  @compression_method,
280
- @time.to_binary_dos_time, # @last_mod_time ,
281
- @time.to_binary_dos_date, # @last_mod_date ,
290
+ @time.to_binary_dos_time, # @last_mod_time
291
+ @time.to_binary_dos_date, # @last_mod_date
282
292
  @crc,
283
293
  zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
284
294
  zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
@@ -353,21 +363,24 @@ module Zip
353
363
 
354
364
  def check_c_dir_entry_static_header_length(buf)
355
365
  return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
366
+
356
367
  raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
357
368
  end
358
369
 
359
370
  def check_c_dir_entry_signature
360
371
  return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
372
+
361
373
  raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
362
374
  end
363
375
 
364
376
  def check_c_dir_entry_comment_size
365
377
  return if @comment && @comment.bytesize == @comment_length
378
+
366
379
  raise ::Zip::Error, 'Truncated cdir zip entry header'
367
380
  end
368
381
 
369
382
  def read_c_dir_extra_field(io)
370
- if @extra.is_a?(::Zip::ExtraField)
383
+ if @extra.kind_of?(::Zip::ExtraField)
371
384
  @extra.merge(io.read(@extra_length))
372
385
  else
373
386
  @extra = ::Zip::ExtraField.new(io.read(@extra_length))
@@ -401,20 +414,25 @@ module Zip
401
414
 
402
415
  def get_extra_attributes_from_path(path) # :nodoc:
403
416
  return if Zip::RUNNING_ON_WINDOWS
417
+
404
418
  stat = file_stat(path)
405
419
  @unix_uid = stat.uid
406
420
  @unix_gid = stat.gid
407
421
  @unix_perms = stat.mode & 0o7777
422
+ @time = ::Zip::DOSTime.from_time(stat.mtime)
408
423
  end
409
424
 
410
- def set_unix_permissions_on_path(dest_path)
411
- # BUG: does not update timestamps into account
425
+ def set_unix_attributes_on_path(dest_path)
412
426
  # ignore setuid/setgid bits by default. honor if @restore_ownership
413
427
  unix_perms_mask = 0o1777
414
428
  unix_perms_mask = 0o7777 if @restore_ownership
415
429
  ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
416
430
  ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
417
- # File::utimes()
431
+
432
+ # Restore the timestamp on a file. This will either have come from the
433
+ # original source file that was copied into the archive, or from the
434
+ # creation date of the archive if there was no original source file.
435
+ ::FileUtils.touch(dest_path, mtime: time) if @restore_times
418
436
  end
419
437
 
420
438
  def set_extra_attributes_on_path(dest_path) # :nodoc:
@@ -422,7 +440,7 @@ module Zip
422
440
 
423
441
  case @fstype
424
442
  when ::Zip::FSTYPE_UNIX
425
- set_unix_permissions_on_path(dest_path)
443
+ set_unix_attributes_on_path(dest_path)
426
444
  end
427
445
  end
428
446
 
@@ -432,11 +450,11 @@ module Zip
432
450
  @header_signature,
433
451
  @version, # version of encoding software
434
452
  @fstype, # filesystem type
435
- @version_needed_to_extract, # @versionNeededToExtract ,
436
- @gp_flags, # @gp_flags ,
453
+ @version_needed_to_extract, # @versionNeededToExtract
454
+ @gp_flags, # @gp_flags
437
455
  @compression_method,
438
- @time.to_binary_dos_time, # @last_mod_time ,
439
- @time.to_binary_dos_date, # @last_mod_date ,
456
+ @time.to_binary_dos_time, # @last_mod_time
457
+ @time.to_binary_dos_date, # @last_mod_date
440
458
  @crc,
441
459
  zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
442
460
  zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
@@ -483,6 +501,7 @@ module Zip
483
501
 
484
502
  def ==(other)
485
503
  return false unless other.class == self.class
504
+
486
505
  # Compares contents of local entry and exposed fields
487
506
  keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
488
507
  other.__send__(k.to_sym) == __send__(k.to_sym)
@@ -590,7 +609,7 @@ module Zip
590
609
  def set_time(binary_dos_date, binary_dos_time)
591
610
  @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
592
611
  rescue ArgumentError
593
- warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date
612
+ warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date
594
613
  end
595
614
 
596
615
  def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
@@ -600,18 +619,29 @@ module Zip
600
619
  end
601
620
  ::File.open(dest_path, 'wb') do |os|
602
621
  get_input_stream do |is|
603
- set_extra_attributes_on_path(dest_path)
604
-
605
- buf = ''
622
+ bytes_written = 0
623
+ warned = false
624
+ buf = +''
606
625
  while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
607
626
  os << buf
627
+ bytes_written += buf.bytesize
628
+ next unless bytes_written > size && !warned
629
+
630
+ message = "entry '#{name}' should be #{size}B, but is larger when inflated."
631
+ raise ::Zip::EntrySizeError, message if ::Zip.validate_entry_sizes
632
+
633
+ warn "WARNING: #{message}"
634
+ warned = true
608
635
  end
609
636
  end
610
637
  end
638
+
639
+ set_extra_attributes_on_path(dest_path)
611
640
  end
612
641
 
613
642
  def create_directory(dest_path)
614
643
  return if ::File.directory?(dest_path)
644
+
615
645
  if ::File.exist?(dest_path)
616
646
  if block_given? && yield(self, dest_path)
617
647
  ::FileUtils.rm_f dest_path
@@ -629,13 +659,14 @@ module Zip
629
659
  def create_symlink(dest_path)
630
660
  # TODO: Symlinks pose security challenges. Symlink support temporarily
631
661
  # removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
632
- puts "WARNING: skipped symlink #{dest_path}"
662
+ warn "WARNING: skipped symlink '#{dest_path}'."
633
663
  end
634
664
 
635
665
  # apply missing data from the zip64 extra information field, if present
636
666
  # (required when file sizes exceed 2**32, but can be used for all files)
637
667
  def parse_zip64_extra(for_local_header) #:nodoc:all
638
668
  return if @extra['Zip64'].nil?
669
+
639
670
  if for_local_header
640
671
  @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
641
672
  else
@@ -650,6 +681,7 @@ module Zip
650
681
  # create a zip64 extra information field if we need one
651
682
  def prep_zip64_extra(for_local_header) #:nodoc:all
652
683
  return unless ::Zip.write_zip64_support
684
+
653
685
  need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
654
686
  need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
655
687
  if need_zip64
@@ -50,6 +50,7 @@ module Zip
50
50
 
51
51
  def ==(other)
52
52
  return false unless other.kind_of?(EntrySet)
53
+
53
54
  @entry_set.values == other.entry_set.values
54
55
  end
55
56
 
@@ -60,6 +61,7 @@ module Zip
60
61
  def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB)
61
62
  entries.map do |entry|
62
63
  next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
64
+
63
65
  yield(entry) if block_given?
64
66
  entry
65
67
  end.compact
@@ -4,8 +4,10 @@ module Zip
4
4
  class DestinationFileExistsError < Error; end
5
5
  class CompressionMethodError < Error; end
6
6
  class EntryNameError < Error; end
7
+ class EntrySizeError < Error; end
7
8
  class InternalError < Error; end
8
9
  class GPFBit3Error < Error; end
10
+ class DecompressionError < Error; end
9
11
 
10
12
  # Backwards compatibility with v1 (delete in v2)
11
13
  ZipError = Error
@@ -6,27 +6,27 @@ module Zip
6
6
  merge(binstr) if binstr
7
7
  end
8
8
 
9
- def extra_field_type_exist(binstr, id, len, i)
9
+ def extra_field_type_exist(binstr, id, len, index)
10
10
  field_name = ID_MAP[id].name
11
11
  if member?(field_name)
12
- self[field_name].merge(binstr[i, len + 4])
12
+ self[field_name].merge(binstr[index, len + 4])
13
13
  else
14
- field_obj = ID_MAP[id].new(binstr[i, len + 4])
14
+ field_obj = ID_MAP[id].new(binstr[index, len + 4])
15
15
  self[field_name] = field_obj
16
16
  end
17
17
  end
18
18
 
19
- def extra_field_type_unknown(binstr, len, i)
19
+ def extra_field_type_unknown(binstr, len, index)
20
20
  create_unknown_item unless self['Unknown']
21
- if !len || len + 4 > binstr[i..-1].bytesize
22
- self['Unknown'] << binstr[i..-1]
21
+ if !len || len + 4 > binstr[index..-1].bytesize
22
+ self['Unknown'] << binstr[index..-1]
23
23
  return
24
24
  end
25
- self['Unknown'] << binstr[i, len + 4]
25
+ self['Unknown'] << binstr[index, len + 4]
26
26
  end
27
27
 
28
28
  def create_unknown_item
29
- s = ''
29
+ s = +''
30
30
  class << s
31
31
  alias_method :to_c_dir_bin, :to_s
32
32
  alias_method :to_local_bin, :to_s
@@ -36,10 +36,11 @@ module Zip
36
36
 
37
37
  def merge(binstr)
38
38
  return if binstr.empty?
39
+
39
40
  i = 0
40
41
  while i < binstr.bytesize
41
42
  id = binstr[i, 2]
42
- len = binstr[i + 2, 2].to_s.unpack('v').first
43
+ len = binstr[i + 2, 2].to_s.unpack1('v')
43
44
  if id && ID_MAP.member?(id)
44
45
  extra_field_type_exist(binstr, id, len, i)
45
46
  elsif id
@@ -54,6 +55,7 @@ module Zip
54
55
  unless (field_class = ID_MAP.values.find { |k| k.name == name })
55
56
  raise Error, "Unknown extra field '#{name}'"
56
57
  end
58
+
57
59
  self[name] = field_class.new
58
60
  end
59
61