rubyzip 3.0.2 → 3.2.2

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.
data/lib/zip/entry.rb CHANGED
@@ -127,10 +127,10 @@ module Zip
127
127
  # Returns modification time by default.
128
128
  def time(component: :mtime)
129
129
  time =
130
- if @extra['UniversalTime']
131
- @extra['UniversalTime'].send(component)
132
- elsif @extra['NTFS']
133
- @extra['NTFS'].send(component)
130
+ if @extra[:universaltime]
131
+ @extra[:universaltime].send(component)
132
+ elsif @extra[:ntfs]
133
+ @extra[:ntfs].send(component)
134
134
  end
135
135
 
136
136
  # Standard time field in central directory has local time
@@ -155,13 +155,13 @@ module Zip
155
155
  # Sets modification time by default.
156
156
  def time=(value, component: :mtime)
157
157
  @dirty = true
158
- unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
159
- @extra.create('UniversalTime')
158
+ unless @extra.member?(:universaltime) || @extra.member?(:ntfs)
159
+ @extra.create(:universaltime)
160
160
  end
161
161
 
162
162
  value = DOSTime.from_time(value)
163
163
  comp = "#{component}=" unless component.to_s.end_with?('=')
164
- (@extra['UniversalTime'] || @extra['NTFS']).send(comp, value)
164
+ (@extra[:universaltime] || @extra[:ntfs]).send(comp, value)
165
165
  @time = value if component == :mtime
166
166
  end
167
167
 
@@ -179,7 +179,7 @@ module Zip
179
179
 
180
180
  # Does this entry return time fields with accurate timezone information?
181
181
  def absolute_time?
182
- @extra.member?('UniversalTime') || @extra.member?('NTFS')
182
+ @extra.member?(:universaltime) || @extra.member?(:ntfs)
183
183
  end
184
184
 
185
185
  # Return the compression method for this entry.
@@ -200,7 +200,12 @@ module Zip
200
200
 
201
201
  # Does this entry use the ZIP64 extensions?
202
202
  def zip64?
203
- !@extra['Zip64'].nil?
203
+ !@extra[:zip64].nil?
204
+ end
205
+
206
+ # Is this entry encrypted with AES encryption?
207
+ def aes?
208
+ !@extra[:aes].nil?
204
209
  end
205
210
 
206
211
  def file_type_is?(type) # :nodoc:
@@ -382,11 +387,12 @@ module Zip
382
387
 
383
388
  read_extra_field(extra, local: true)
384
389
  parse_zip64_extra(true)
390
+ parse_aes_extra
385
391
  @local_header_size = calculate_local_header_size
386
392
  end
387
393
 
388
394
  def pack_local_entry # :nodoc:
389
- zip64 = @extra['Zip64']
395
+ zip64 = @extra[:zip64]
390
396
  [::Zip::LOCAL_ENTRY_SIGNATURE,
391
397
  @version_needed_to_extract, # version needed to extract
392
398
  @gp_flags, # @gp_flags
@@ -400,9 +406,17 @@ module Zip
400
406
  @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
401
407
  end
402
408
 
403
- def write_local_entry(io, rewrite: false) # :nodoc:
409
+ def write_local_entry(io, suppress_extra_fields: false, rewrite: false) # :nodoc:
404
410
  prep_local_zip64_extra
405
- verify_local_header_size! if rewrite
411
+
412
+ # If we are rewriting the local header, then we verify that we haven't changed
413
+ # its size. At this point we have to keep extra fields if they are present.
414
+ if rewrite
415
+ verify_local_header_size!
416
+ elsif suppress_extra_fields
417
+ @extra.suppress_fields!(suppress_extra_fields)
418
+ end
419
+
406
420
  @local_header_offset = io.tell
407
421
 
408
422
  io << pack_local_entry
@@ -430,10 +444,7 @@ module Zip
430
444
  _, # diskNumberStart
431
445
  @internal_file_attributes,
432
446
  @external_file_attributes,
433
- @local_header_offset,
434
- @name,
435
- @extra,
436
- @comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
447
+ @local_header_offset = buf.unpack('VCCvvvvvVVVvvvvvVV')
437
448
  end
438
449
 
439
450
  def set_ftype_from_c_dir_entry # :nodoc:
@@ -511,6 +522,7 @@ module Zip
511
522
  check_c_dir_entry_comment_size
512
523
  set_ftype_from_c_dir_entry
513
524
  parse_zip64_extra(false)
525
+ parse_aes_extra
514
526
  end
515
527
 
516
528
  def file_stat(path) # :nodoc:
@@ -559,7 +571,7 @@ module Zip
559
571
  end
560
572
 
561
573
  def pack_c_dir_entry # :nodoc:
562
- zip64 = @extra['Zip64']
574
+ zip64 = @extra[:zip64]
563
575
  [
564
576
  @header_signature,
565
577
  @version, # version of encoding software
@@ -578,14 +590,11 @@ module Zip
578
590
  zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
579
591
  @internal_file_attributes, # file type (binary=0, text=1)
580
592
  @external_file_attributes, # native filesystem attributes
581
- zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
582
- @name,
583
- @extra,
584
- @comment
593
+ zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
585
594
  ].pack('VCCvvvvvVVVvvvvvVV')
586
595
  end
587
596
 
588
- def write_c_dir_entry(io) # :nodoc:
597
+ def write_c_dir_entry(io, suppress_extra_fields: false) # :nodoc:
589
598
  prep_cdir_zip64_extra
590
599
 
591
600
  case @fstype
@@ -607,6 +616,7 @@ module Zip
607
616
  end
608
617
  end
609
618
 
619
+ @extra.suppress_fields!(suppress_extra_fields) if suppress_extra_fields
610
620
  io << pack_c_dir_entry
611
621
 
612
622
  io << @name
@@ -667,7 +677,7 @@ module Zip
667
677
  when 'file'
668
678
  if name_is_directory?
669
679
  raise ArgumentError,
670
- "entry name '#{newEntry}' indicates directory entry, but " \
680
+ "entry name '#{@name}' indicates a directory entry, but " \
671
681
  "'#{src_path}' is not a directory"
672
682
  end
673
683
  :file
@@ -677,7 +687,7 @@ module Zip
677
687
  when 'link'
678
688
  if name_is_directory?
679
689
  raise ArgumentError,
680
- "entry name '#{newEntry}' indicates directory entry, but " \
690
+ "entry name '#{@name}' indicates a directory entry, but " \
681
691
  "'#{src_path}' is not a directory"
682
692
  end
683
693
  :symlink
@@ -792,14 +802,28 @@ module Zip
792
802
  return unless zip64?
793
803
 
794
804
  if for_local_header
795
- @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
805
+ @size, @compressed_size = @extra[:zip64].parse(@size, @compressed_size)
796
806
  else
797
- @size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(
807
+ @size, @compressed_size, @local_header_offset = @extra[:zip64].parse(
798
808
  @size, @compressed_size, @local_header_offset
799
809
  )
800
810
  end
801
811
  end
802
812
 
813
+ def parse_aes_extra # :nodoc:
814
+ return unless aes?
815
+
816
+ if @extra[:aes].vendor_id != 'AE'
817
+ raise Error, "Unsupported encryption method #{@extra[:aes].vendor_id}"
818
+ end
819
+
820
+ unless ::Zip::AESEncryption::VERSIONS.include? @extra[:aes].vendor_version
821
+ raise Error, "Unsupported encryption style #{@extra[:aes].vendor_version}"
822
+ end
823
+
824
+ @compression_method = @extra[:aes].compression_method if ftype != :directory
825
+ end
826
+
803
827
  # For DEFLATED compression *only*: set the general purpose flags 1 and 2 to
804
828
  # indicate compression level. This seems to be mainly cosmetic but they are
805
829
  # generally set by other tools - including in docx files. It is these flags
@@ -830,7 +854,7 @@ module Zip
830
854
  # If we already have a ZIP64 extra (placeholder) then we must fill it in.
831
855
  if zip64? || @size.nil? || @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
832
856
  @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
833
- zip64 = @extra['Zip64'] || @extra.create('Zip64')
857
+ zip64 = @extra[:zip64] || @extra.create(:zip64)
834
858
 
835
859
  # Local header always includes size and compressed size.
836
860
  zip64.original_size = @size || 0
@@ -844,7 +868,7 @@ module Zip
844
868
  if (@size && @size >= 0xFFFFFFFF) || @compressed_size >= 0xFFFFFFFF ||
845
869
  @local_header_offset >= 0xFFFFFFFF
846
870
  @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
847
- zip64 = @extra['Zip64'] || @extra.create('Zip64')
871
+ zip64 = @extra[:zip64] || @extra.create(:zip64)
848
872
 
849
873
  # Central directory entry entries include whichever fields are necessary.
850
874
  zip64.original_size = @size if @size && @size >= 0xFFFFFFFF
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ # Info-ZIP Extra for AES encryption
5
+ class ExtraField::AES < ExtraField::Generic # :nodoc:
6
+ attr_reader :vendor_version, :vendor_id, :encryption_strength, :compression_method
7
+
8
+ HEADER_ID = [0x9901].pack('v')
9
+ register_map
10
+
11
+ def initialize(binstr = nil)
12
+ @vendor_version = nil
13
+ @vendor_id = nil
14
+ @encryption_strength = nil
15
+ @compression_method = nil
16
+ binstr && merge(binstr)
17
+ end
18
+
19
+ def ==(other)
20
+ @vendor_version == other.vendor_version &&
21
+ @vendor_id == other.vendor_id &&
22
+ @encryption_strength == other.encryption_strength &&
23
+ @compression_method == other.compression_method
24
+ end
25
+
26
+ def merge(binstr)
27
+ return if binstr.empty?
28
+
29
+ size, content = initial_parse(binstr)
30
+ (size && content) || return
31
+
32
+ @vendor_version, @vendor_id,
33
+ @encryption_strength, @compression_method = content.unpack('va2Cv')
34
+ end
35
+
36
+ # We can never suppress the AES extra field as it is needed to read the file.
37
+ def suppress?
38
+ false
39
+ end
40
+
41
+ def pack_for_local
42
+ [@vendor_version, @vendor_id,
43
+ @encryption_strength, @compression_method].pack('va2Cv')
44
+ end
45
+
46
+ def pack_for_c_dir
47
+ pack_for_local
48
+ end
49
+ end
50
+ end
@@ -9,7 +9,7 @@ module Zip
9
9
  end
10
10
 
11
11
  def self.name
12
- @name ||= to_s.split('::')[-1]
12
+ @name ||= to_s.split('::').last.downcase.to_sym
13
13
  end
14
14
 
15
15
  # return field [size, content] or false
@@ -24,6 +24,12 @@ module Zip
24
24
  [binstr[2, 2].unpack1('v'), binstr[4..]]
25
25
  end
26
26
 
27
+ # Default strategy is to suppress all extra fields if we're asked to.
28
+ # Specific extra field types can override this if they need to be kept.
29
+ def suppress?
30
+ true
31
+ end
32
+
27
33
  def to_local_bin
28
34
  s = pack_for_local
29
35
  (self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'generic'
4
+
3
5
  module Zip
4
6
  # A class to hold unknown extra fields so that they are preserved.
5
- class ExtraField::Unknown # :nodoc:
7
+ class ExtraField::Unknown < ExtraField::Generic # :nodoc:
6
8
  def initialize
7
9
  @local_bin = +''
8
10
  @cdir_bin = +''
@@ -56,6 +56,13 @@ module Zip
56
56
  end
57
57
  private :extract
58
58
 
59
+ # We can suppress the zip64 extra field unless we know the size is large or
60
+ # the relative header offset is large (for central directory entries).
61
+ def suppress?
62
+ !(@original_size && @original_size >= 0xFFFFFFFF) ||
63
+ (@relative_header_offset && @relative_header_offset >= 0xFFFFFFFF)
64
+ end
65
+
59
66
  def pack_for_local
60
67
  # Local header entries must contain original size and compressed size;
61
68
  # other fields do not apply.
@@ -19,14 +19,14 @@ module Zip
19
19
  end
20
20
 
21
21
  def extra_field_type_unknown(binstr, len, index, local)
22
- self['Unknown'] ||= Unknown.new
22
+ self[:unknown] ||= Unknown.new
23
23
 
24
24
  if !len || len + 4 > binstr[index..].bytesize
25
- self['Unknown'].merge(binstr[index..], local: local)
25
+ self[:unknown].merge(binstr[index..], local: local)
26
26
  return
27
27
  end
28
28
 
29
- self['Unknown'].merge(binstr[index, len + 4], local: local)
29
+ self[:unknown].merge(binstr[index, len + 4], local: local)
30
30
  end
31
31
 
32
32
  def merge(binstr, local: false)
@@ -57,10 +57,17 @@ module Zip
57
57
  # signature/size does not prevent known fields from being read back in.
58
58
  def ordered_values
59
59
  result = []
60
- each { |k, v| k == 'Unknown' ? result.push(v) : result.unshift(v) }
60
+ each { |k, v| k == :unknown ? result.push(v) : result.unshift(v) }
61
61
  result
62
62
  end
63
63
 
64
+ # Remove any extra fields that indicate they can be safely suppressed.
65
+ def suppress_fields!(fields)
66
+ reject! do |k, v|
67
+ v.suppress? if fields == true || [*fields].include?(k)
68
+ end
69
+ end
70
+
64
71
  def to_local_bin
65
72
  ordered_values.map! { |v| v.to_local_bin.force_encoding('BINARY') }.join
66
73
  end
@@ -91,6 +98,7 @@ require 'zip/extra_field/old_unix'
91
98
  require 'zip/extra_field/unix'
92
99
  require 'zip/extra_field/zip64'
93
100
  require 'zip/extra_field/ntfs'
101
+ require 'zip/extra_field/aes'
94
102
 
95
103
  # Copyright (C) 2002, 2003 Thomas Sondergaard
96
104
  # rubyzip is free software; you can redistribute it and/or
data/lib/zip/file.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fileutils'
3
4
  require 'forwardable'
4
5
 
5
6
  require_relative 'file_split'
@@ -75,7 +76,8 @@ module Zip
75
76
  restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
76
77
  restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
77
78
  restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
78
- compression_level: ::Zip.default_compression)
79
+ compression_level: ::Zip.default_compression,
80
+ suppress_extra_fields: false)
79
81
  super()
80
82
 
81
83
  @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
@@ -83,10 +85,11 @@ module Zip
83
85
 
84
86
  initialize_cdir(path_or_io, buffer: buffer)
85
87
 
86
- @restore_ownership = restore_ownership
87
- @restore_permissions = restore_permissions
88
- @restore_times = restore_times
89
- @compression_level = compression_level
88
+ @restore_ownership = restore_ownership
89
+ @restore_permissions = restore_permissions
90
+ @restore_times = restore_times
91
+ @compression_level = compression_level
92
+ @suppress_extra_fields = suppress_extra_fields
90
93
  end
91
94
 
92
95
  class << self
@@ -97,13 +100,14 @@ module Zip
97
100
  restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
98
101
  restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
99
102
  restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
100
- compression_level: ::Zip.default_compression)
101
-
102
- zf = ::Zip::File.new(file_name, create: create,
103
- restore_ownership: restore_ownership,
104
- restore_permissions: restore_permissions,
105
- restore_times: restore_times,
106
- compression_level: compression_level)
103
+ compression_level: ::Zip.default_compression,
104
+ suppress_extra_fields: false)
105
+ zf = ::Zip::File.new(file_name, create: create,
106
+ restore_ownership: restore_ownership,
107
+ restore_permissions: restore_permissions,
108
+ restore_times: restore_times,
109
+ compression_level: compression_level,
110
+ suppress_extra_fields: suppress_extra_fields)
107
111
 
108
112
  return zf unless block_given?
109
113
 
@@ -122,8 +126,8 @@ module Zip
122
126
  restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
123
127
  restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
124
128
  restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
125
- compression_level: ::Zip.default_compression)
126
-
129
+ compression_level: ::Zip.default_compression,
130
+ suppress_extra_fields: false)
127
131
  unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
128
132
  raise 'Zip::File.open_buffer expects a String or IO-like argument' \
129
133
  "(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
@@ -132,10 +136,11 @@ module Zip
132
136
  io = ::StringIO.new(io) if io.kind_of?(::String)
133
137
 
134
138
  zf = ::Zip::File.new(io, create: create, buffer: true,
135
- restore_ownership: restore_ownership,
136
- restore_permissions: restore_permissions,
137
- restore_times: restore_times,
138
- compression_level: compression_level)
139
+ restore_ownership: restore_ownership,
140
+ restore_permissions: restore_permissions,
141
+ restore_times: restore_times,
142
+ compression_level: compression_level,
143
+ suppress_extra_fields: suppress_extra_fields)
139
144
 
140
145
  return zf unless block_given?
141
146
 
@@ -191,7 +196,6 @@ module Zip
191
196
  extra: nil, compressed_size: nil, crc: nil,
192
197
  compression_method: nil, compression_level: nil,
193
198
  size: nil, time: nil, &a_proc)
194
-
195
199
  new_entry =
196
200
  if entry.kind_of?(Entry)
197
201
  entry
@@ -288,7 +292,7 @@ module Zip
288
292
  return if name.kind_of?(StringIO) || !commit_required?
289
293
 
290
294
  on_success_replace do |tmp_file|
291
- ::Zip::OutputStream.open(tmp_file) do |zos|
295
+ ::Zip::OutputStream.open(tmp_file, suppress_extra_fields: @suppress_extra_fields) do |zos|
292
296
  @cdir.each do |e|
293
297
  e.write_to_zip_output_stream(zos)
294
298
  e.clean_up
@@ -304,7 +308,7 @@ module Zip
304
308
  def write_buffer(io = ::StringIO.new)
305
309
  return io unless commit_required?
306
310
 
307
- ::Zip::OutputStream.write_buffer(io) do |zos|
311
+ ::Zip::OutputStream.write_buffer(io, suppress_extra_fields: @suppress_extra_fields) do |zos|
308
312
  @cdir.each { |e| e.write_to_zip_output_stream(zos) }
309
313
  zos.comment = comment
310
314
  end
@@ -412,7 +416,7 @@ module Zip
412
416
  ::File.chmod(@file_permissions, name) unless @create
413
417
  end
414
418
  ensure
415
- ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
419
+ FileUtils.rm_f(tmp_filename)
416
420
  end
417
421
  end
418
422
  end
@@ -27,7 +27,7 @@ module Zip
27
27
 
28
28
  def unix_mode_cmp(filename, mode)
29
29
  e = find_entry(filename)
30
- e.fstype == FSTYPE_UNIX && ((e.external_file_attributes >> 16) & mode) != 0
30
+ e.fstype == FSTYPE_UNIX && (e.external_file_attributes >> 16).anybits?(mode)
31
31
  rescue Errno::ENOENT
32
32
  false
33
33
  end
@@ -102,18 +102,19 @@ module Zip
102
102
  @mapped_zip.get_entry(filename).size
103
103
  end
104
104
 
105
- # Returns nil for not found and nil for directories
105
+ # Returns nil for not found and nil for directories.
106
+ # We disable the cop here for compatibility with `::File.size?`.
106
107
  def size?(filename)
107
108
  entry = @mapped_zip.find_entry(filename)
108
- entry.nil? || entry.directory? ? nil : entry.size
109
+ entry.nil? || entry.directory? ? nil : entry.size # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
109
110
  end
110
111
 
111
112
  def chown(owner, group, *filenames)
112
113
  filenames.each do |filename|
113
114
  e = find_entry(filename)
114
- e.extra.create('IUnix') unless e.extra.member?('IUnix')
115
- e.extra['IUnix'].uid = owner
116
- e.extra['IUnix'].gid = group
115
+ e.extra.create(:iunix) unless e.extra.member?(:iunix)
116
+ e.extra[:iunix].uid = owner
117
+ e.extra[:iunix].gid = group
117
118
  end
118
119
  filenames.size
119
120
  end
@@ -36,8 +36,8 @@ module Zip
36
36
 
37
37
  def gid
38
38
  e = find_entry
39
- if e.extra.member? 'IUnix'
40
- e.extra['IUnix'].gid || 0
39
+ if e.extra.member? :iunix
40
+ e.extra[:iunix].gid || 0
41
41
  else
42
42
  0
43
43
  end
@@ -45,8 +45,8 @@ module Zip
45
45
 
46
46
  def uid
47
47
  e = find_entry
48
- if e.extra.member? 'IUnix'
49
- e.extra['IUnix'].uid || 0
48
+ if e.extra.member? :iunix
49
+ e.extra[:iunix].uid || 0
50
50
  else
51
51
  0
52
52
  end
data/lib/zip/inflater.rb CHANGED
@@ -10,7 +10,7 @@ module Zip
10
10
  end
11
11
 
12
12
  def read(length = nil, outbuf = +'')
13
- return (length.nil? || length.zero? ? '' : nil) if eof
13
+ return (length.nil? || length.zero? ? '' : nil) if eof?
14
14
 
15
15
  while length.nil? || (@buffer.bytesize < length)
16
16
  break if input_finished?
@@ -21,11 +21,12 @@ module Zip
21
21
  outbuf.replace(@buffer.slice!(0...(length || @buffer.bytesize)))
22
22
  end
23
23
 
24
- def eof
24
+ def eof?
25
25
  @buffer.empty? && input_finished?
26
26
  end
27
27
 
28
- alias eof? eof
28
+ # Alias for compatibility. Remove for version 4.
29
+ alias eof eof?
29
30
 
30
31
  private
31
32
 
@@ -55,7 +55,7 @@ module Zip
55
55
  super()
56
56
  @archive_io = get_io(context, offset)
57
57
  @decompressor = ::Zip::NullDecompressor
58
- @decrypter = decrypter || ::Zip::NullDecrypter.new
58
+ @decrypter = decrypter
59
59
  @current_entry = nil
60
60
  @complete_entry = nil
61
61
  end
@@ -135,29 +135,48 @@ module Zip
135
135
  @current_entry = ::Zip::Entry.read_local_entry(@archive_io)
136
136
  return if @current_entry.nil?
137
137
 
138
- if @current_entry.encrypted? && @decrypter.kind_of?(NullDecrypter)
139
- raise Error,
140
- 'A password is required to decode this zip file'
141
- end
142
-
143
138
  if @current_entry.incomplete? && @current_entry.compressed_size == 0 && !@complete_entry
144
139
  raise StreamingError, @current_entry
145
140
  end
146
141
 
147
- @decrypted_io = get_decrypted_io
148
- @decompressor = get_decompressor
142
+ @decompressor = assemble_io
149
143
  flush
150
144
  @current_entry
151
145
  end
152
146
 
147
+ def assemble_io # :nodoc:
148
+ io = if @current_entry.encrypted?
149
+ raise Error, 'A password is required to decode this zip file.' if @decrypter.nil?
150
+
151
+ get_decrypted_io
152
+ else
153
+ @archive_io
154
+ end
155
+
156
+ get_decompressor(io)
157
+ end
158
+
153
159
  def get_decrypted_io # :nodoc:
154
160
  header = @archive_io.read(@decrypter.header_bytesize)
155
161
  @decrypter.reset!(header)
156
162
 
157
- ::Zip::DecryptedIo.new(@archive_io, @decrypter)
163
+ compressed_size =
164
+ if @current_entry.incomplete? && @current_entry.crc == 0 &&
165
+ @current_entry.compressed_size == 0 && @complete_entry
166
+ @complete_entry.compressed_size
167
+ else
168
+ @current_entry.compressed_size
169
+ end
170
+
171
+ if @decrypter.kind_of?(::Zip::AESDecrypter)
172
+ compressed_size -= @decrypter.header_bytesize
173
+ compressed_size -= ::Zip::AESEncryption::AUTHENTICATION_CODE_LENGTH
174
+ end
175
+
176
+ ::Zip::DecryptedIo.new(@archive_io, @decrypter, compressed_size)
158
177
  end
159
178
 
160
- def get_decompressor # :nodoc:
179
+ def get_decompressor(io) # :nodoc:
161
180
  return ::Zip::NullDecompressor if @current_entry.nil?
162
181
 
163
182
  decompressed_size =
@@ -175,7 +194,7 @@ module Zip
175
194
  raise ::Zip::CompressionMethodError, @current_entry.compression_method
176
195
  end
177
196
 
178
- decompressor_class.new(@decrypted_io, decompressed_size)
197
+ decompressor_class.new(io, decompressed_size)
179
198
  end
180
199
 
181
200
  def produce_input # :nodoc:
@@ -183,7 +202,7 @@ module Zip
183
202
  end
184
203
 
185
204
  def input_finished? # :nodoc:
186
- @decompressor.eof
205
+ @decompressor.eof?
187
206
  end
188
207
  end
189
208
  end
@@ -117,11 +117,12 @@ module Zip
117
117
 
118
118
  alias each each_line
119
119
 
120
- def eof
120
+ def eof?
121
121
  @output_buffer.empty? && input_finished?
122
122
  end
123
123
 
124
- alias eof? eof
124
+ # Alias for compatibility. Remove for version 4.
125
+ alias eof eof?
125
126
  end
126
127
  end
127
128
  end
@@ -8,11 +8,12 @@ module Zip
8
8
  nil
9
9
  end
10
10
 
11
- def eof
11
+ def eof?
12
12
  true
13
13
  end
14
14
 
15
- alias eof? eof
15
+ # Alias for compatibility. Remove for version 4.
16
+ alias eof eof?
16
17
  end
17
18
  end
18
19