rubyzip 2.4.1 → 3.2.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +476 -0
  3. data/LICENSE.md +24 -0
  4. data/README.md +180 -40
  5. data/Rakefile +15 -13
  6. data/lib/zip/central_directory.rb +172 -124
  7. data/lib/zip/compressor.rb +3 -1
  8. data/lib/zip/constants.rb +29 -21
  9. data/lib/zip/crypto/aes_encryption.rb +120 -0
  10. data/lib/zip/crypto/decrypted_io.rb +20 -14
  11. data/lib/zip/crypto/encryption.rb +4 -2
  12. data/lib/zip/crypto/null_encryption.rb +5 -13
  13. data/lib/zip/crypto/traditional_encryption.rb +10 -6
  14. data/lib/zip/decompressor.rb +4 -3
  15. data/lib/zip/deflater.rb +12 -8
  16. data/lib/zip/dirtyable.rb +32 -0
  17. data/lib/zip/dos_time.rb +45 -5
  18. data/lib/zip/entry.rb +391 -264
  19. data/lib/zip/entry_set.rb +11 -9
  20. data/lib/zip/errors.rb +136 -16
  21. data/lib/zip/extra_field/aes.rb +50 -0
  22. data/lib/zip/extra_field/generic.rb +10 -11
  23. data/lib/zip/extra_field/ntfs.rb +6 -4
  24. data/lib/zip/extra_field/old_unix.rb +3 -1
  25. data/lib/zip/extra_field/universal_time.rb +3 -1
  26. data/lib/zip/extra_field/unix.rb +5 -3
  27. data/lib/zip/extra_field/unknown.rb +35 -0
  28. data/lib/zip/extra_field/zip64.rb +19 -5
  29. data/lib/zip/extra_field.rb +25 -23
  30. data/lib/zip/file.rb +174 -267
  31. data/lib/zip/file_split.rb +91 -0
  32. data/lib/zip/filesystem/dir.rb +86 -0
  33. data/lib/zip/filesystem/directory_iterator.rb +48 -0
  34. data/lib/zip/filesystem/file.rb +263 -0
  35. data/lib/zip/filesystem/file_stat.rb +110 -0
  36. data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
  37. data/lib/zip/filesystem.rb +27 -596
  38. data/lib/zip/inflater.rb +11 -8
  39. data/lib/zip/input_stream.rb +76 -57
  40. data/lib/zip/ioextras/abstract_input_stream.rb +19 -13
  41. data/lib/zip/ioextras/abstract_output_stream.rb +13 -3
  42. data/lib/zip/ioextras.rb +7 -7
  43. data/lib/zip/null_compressor.rb +3 -1
  44. data/lib/zip/null_decompressor.rb +6 -3
  45. data/lib/zip/null_input_stream.rb +3 -1
  46. data/lib/zip/output_stream.rb +60 -57
  47. data/lib/zip/pass_thru_compressor.rb +3 -1
  48. data/lib/zip/pass_thru_decompressor.rb +8 -5
  49. data/lib/zip/streamable_directory.rb +3 -1
  50. data/lib/zip/streamable_stream.rb +4 -1
  51. data/lib/zip/version.rb +4 -1
  52. data/lib/zip.rb +25 -22
  53. data/rubyzip.gemspec +39 -0
  54. data/samples/example.rb +8 -3
  55. data/samples/example_filesystem.rb +3 -2
  56. data/samples/example_recursive.rb +3 -1
  57. data/samples/gtk_ruby_zip.rb +5 -3
  58. data/samples/qtzip.rb +7 -6
  59. data/samples/write_simple.rb +2 -1
  60. data/samples/zipfind.rb +1 -0
  61. metadata +86 -52
  62. data/TODO +0 -15
  63. data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
1
4
  module Zip
2
5
  # InputStream is the basic class for reading zip entries in a
3
6
  # zip file. It is possible to create a InputStream object directly,
@@ -37,9 +40,8 @@ module Zip
37
40
  #
38
41
  # java.util.zip.ZipInputStream is the original inspiration for this
39
42
  # class.
40
-
41
43
  class InputStream
42
- CHUNK_SIZE = 32_768
44
+ CHUNK_SIZE = 32_768 # :nodoc:
43
45
 
44
46
  include ::Zip::IOExtras::AbstractInputStream
45
47
 
@@ -49,34 +51,35 @@ module Zip
49
51
  #
50
52
  # @param context [String||IO||StringIO] file path or IO/StringIO object
51
53
  # @param offset [Integer] offset in the IO/StringIO
52
- def initialize(context, dep_offset = 0, dep_decrypter = nil, offset: 0, decrypter: nil)
54
+ def initialize(context, offset: 0, decrypter: nil)
53
55
  super()
54
-
55
- if !dep_offset.zero? || !dep_decrypter.nil?
56
- Zip.warn_about_v3_api('Zip::InputStream.new')
57
- end
58
-
59
- offset = dep_offset if offset.zero?
60
- @archive_io = get_io(context, offset)
61
- @decompressor = ::Zip::NullDecompressor
62
- @decrypter = decrypter || dep_decrypter || ::Zip::NullDecrypter.new
56
+ @archive_io = get_io(context, offset)
57
+ @decompressor = ::Zip::NullDecompressor
58
+ @decrypter = decrypter
63
59
  @current_entry = nil
60
+ @complete_entry = nil
64
61
  end
65
62
 
63
+ # Close this InputStream. All further IO will raise an IOError.
66
64
  def close
67
65
  @archive_io.close
68
66
  end
69
67
 
70
- # Returns a Entry object. It is necessary to call this
71
- # method on a newly created InputStream before reading from
72
- # the first entry in the archive. Returns nil when there are
73
- # no more entries.
68
+ # Returns an Entry object and positions the stream at the beginning of
69
+ # the entry data. It is necessary to call this method on a newly created
70
+ # InputStream before reading from the first entry in the archive.
71
+ # Returns nil when there are no more entries.
74
72
  def get_next_entry
75
- @archive_io.seek(@current_entry.next_header_offset, IO::SEEK_SET) if @current_entry
73
+ unless @current_entry.nil?
74
+ raise StreamingError, @current_entry if @current_entry.incomplete?
75
+
76
+ @archive_io.seek(@current_entry.next_header_offset, IO::SEEK_SET)
77
+ end
78
+
76
79
  open_entry
77
80
  end
78
81
 
79
- # Rewinds the stream to the beginning of the current entry
82
+ # Rewinds the stream to the beginning of the current entry.
80
83
  def rewind
81
84
  return if @current_entry.nil?
82
85
 
@@ -87,22 +90,23 @@ module Zip
87
90
  end
88
91
 
89
92
  # Modeled after IO.sysread
90
- def sysread(length = nil, outbuf = '')
93
+ def sysread(length = nil, outbuf = +'')
91
94
  @decompressor.read(length, outbuf)
92
95
  end
93
96
 
97
+ # Returns the size of the current entry, or `nil` if there isn't one.
98
+ def size
99
+ return if @current_entry.nil?
100
+
101
+ @current_entry.size
102
+ end
103
+
94
104
  class << self
95
105
  # Same as #initialize but if a block is passed the opened
96
106
  # stream is passed to the block and closed when the block
97
107
  # returns.
98
- def open(filename_or_io, dep_offset = 0, dep_decrypter = nil, offset: 0, decrypter: nil)
99
- if !dep_offset.zero? || !dep_decrypter.nil?
100
- Zip.warn_about_v3_api('Zip::InputStream.new')
101
- end
102
-
103
- offset = dep_offset if offset.zero?
104
-
105
- zio = new(filename_or_io, offset: offset, decrypter: decrypter || dep_decrypter)
108
+ def open(filename_or_io, offset: 0, decrypter: nil)
109
+ zio = new(filename_or_io, offset: offset, decrypter: decrypter)
106
110
  return zio unless block_given?
107
111
 
108
112
  begin
@@ -111,17 +115,11 @@ module Zip
111
115
  zio.close if zio
112
116
  end
113
117
  end
114
-
115
- def open_buffer(filename_or_io, offset = 0)
116
- Zip.warn_about_v3_api('Zip::InputStream.open_buffer')
117
-
118
- ::Zip::InputStream.open(filename_or_io, offset)
119
- end
120
118
  end
121
119
 
122
120
  protected
123
121
 
124
- def get_io(io_or_file, offset = 0)
122
+ def get_io(io_or_file, offset = 0) # :nodoc:
125
123
  if io_or_file.respond_to?(:seek)
126
124
  io = io_or_file.dup
127
125
  io.seek(offset, ::IO::SEEK_SET)
@@ -133,57 +131,78 @@ module Zip
133
131
  end
134
132
  end
135
133
 
136
- def open_entry
134
+ def open_entry # :nodoc:
137
135
  @current_entry = ::Zip::Entry.read_local_entry(@archive_io)
138
- if @current_entry && @current_entry.encrypted? && @decrypter.kind_of?(NullEncrypter)
139
- raise Error, 'password required to decode zip file'
140
- end
136
+ return if @current_entry.nil?
141
137
 
142
- if @current_entry && @current_entry.incomplete? && @current_entry.crc == 0 \
143
- && @current_entry.compressed_size == 0 \
144
- && @current_entry.size == 0 && !@complete_entry
145
- raise GPFBit3Error,
146
- 'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \
147
- 'Please use ::Zip::File instead of ::Zip::InputStream'
138
+ if @current_entry.incomplete? && @current_entry.compressed_size == 0 && !@complete_entry
139
+ raise StreamingError, @current_entry
148
140
  end
149
- @decrypted_io = get_decrypted_io
150
- @decompressor = get_decompressor
141
+
142
+ @decompressor = assemble_io
151
143
  flush
152
144
  @current_entry
153
145
  end
154
146
 
155
- def get_decrypted_io
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
+
159
+ def get_decrypted_io # :nodoc:
156
160
  header = @archive_io.read(@decrypter.header_bytesize)
157
161
  @decrypter.reset!(header)
158
162
 
159
- ::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)
160
177
  end
161
178
 
162
- def get_decompressor
179
+ def get_decompressor(io) # :nodoc:
163
180
  return ::Zip::NullDecompressor if @current_entry.nil?
164
181
 
165
182
  decompressed_size =
166
- if @current_entry.incomplete? && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry
183
+ if @current_entry.incomplete? && @current_entry.crc == 0 &&
184
+ @current_entry.size == 0 && @complete_entry
167
185
  @complete_entry.size
168
186
  else
169
187
  @current_entry.size
170
188
  end
171
189
 
172
- decompressor_class = ::Zip::Decompressor.find_by_compression_method(@current_entry.compression_method)
190
+ decompressor_class = ::Zip::Decompressor.find_by_compression_method(
191
+ @current_entry.compression_method
192
+ )
173
193
  if decompressor_class.nil?
174
- raise ::Zip::CompressionMethodError,
175
- "Unsupported compression method #{@current_entry.compression_method}"
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
- def produce_input
200
+ def produce_input # :nodoc:
182
201
  @decompressor.read(CHUNK_SIZE)
183
202
  end
184
203
 
185
- def input_finished?
186
- @decompressor.eof
204
+ def input_finished? # :nodoc:
205
+ @decompressor.eof?
187
206
  end
188
207
  end
189
208
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- module IOExtras
4
+ module IOExtras # :nodoc:
3
5
  # Implements many of the convenience methods of IO
4
6
  # such as gets, getc, readline and readlines
5
7
  # depends on: input_finished?, produce_input and read
6
- module AbstractInputStream
8
+ module AbstractInputStream # :nodoc:
7
9
  include Enumerable
8
10
  include FakeIO
9
11
 
@@ -11,22 +13,22 @@ module Zip
11
13
  super
12
14
  @lineno = 0
13
15
  @pos = 0
14
- @output_buffer = ''.b
16
+ @output_buffer = +''
15
17
  end
16
18
 
17
19
  attr_accessor :lineno
18
20
  attr_reader :pos
19
21
 
20
- def read(number_of_bytes = nil, buf = ''.b)
22
+ def read(number_of_bytes = nil, buf = +'')
21
23
  tbuf = if @output_buffer.bytesize > 0
22
- if number_of_bytes <= @output_buffer.bytesize
24
+ if number_of_bytes && number_of_bytes <= @output_buffer.bytesize
23
25
  @output_buffer.slice!(0, number_of_bytes)
24
26
  else
25
27
  number_of_bytes -= @output_buffer.bytesize if number_of_bytes
26
28
  rbuf = sysread(number_of_bytes, buf)
27
29
  out = @output_buffer
28
30
  out << rbuf if rbuf
29
- @output_buffer = ''.b
31
+ @output_buffer = ''
30
32
  out
31
33
  end
32
34
  else
@@ -34,7 +36,7 @@ module Zip
34
36
  end
35
37
 
36
38
  if tbuf.nil? || tbuf.empty?
37
- return nil if number_of_bytes
39
+ return nil if number_of_bytes&.positive?
38
40
 
39
41
  return ''
40
42
  end
@@ -74,15 +76,18 @@ module Zip
74
76
  a_sep_string = "#{$INPUT_RECORD_SEPARATOR}#{$INPUT_RECORD_SEPARATOR}" if a_sep_string.empty?
75
77
 
76
78
  buffer_index = 0
77
- over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes)
79
+ over_limit = number_of_bytes && @output_buffer.bytesize >= number_of_bytes
78
80
  while (match_index = @output_buffer.index(a_sep_string, buffer_index)).nil? && !over_limit
79
81
  buffer_index = [buffer_index, @output_buffer.bytesize - a_sep_string.bytesize].max
80
82
  return @output_buffer.empty? ? nil : flush if input_finished?
81
83
 
82
84
  @output_buffer << produce_input
83
- over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes)
85
+ over_limit = number_of_bytes && @output_buffer.bytesize >= number_of_bytes
84
86
  end
85
- sep_index = [match_index + a_sep_string.bytesize, number_of_bytes || @output_buffer.bytesize].min
87
+ sep_index = [
88
+ match_index + a_sep_string.bytesize,
89
+ number_of_bytes || @output_buffer.bytesize
90
+ ].min
86
91
  @pos += sep_index
87
92
  @output_buffer.slice!(0...sep_index)
88
93
  end
@@ -93,7 +98,7 @@ module Zip
93
98
 
94
99
  def flush
95
100
  ret_val = @output_buffer
96
- @output_buffer = ''.b
101
+ @output_buffer = +''
97
102
  ret_val
98
103
  end
99
104
 
@@ -112,11 +117,12 @@ module Zip
112
117
 
113
118
  alias each each_line
114
119
 
115
- def eof
120
+ def eof?
116
121
  @output_buffer.empty? && input_finished?
117
122
  end
118
123
 
119
- alias eof? eof
124
+ # Alias for compatibility. Remove for version 4.
125
+ alias eof eof?
120
126
  end
121
127
  end
122
128
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- module IOExtras
4
+ module IOExtras # :nodoc:
3
5
  # Implements many of the output convenience methods of IO.
4
6
  # relies on <<
5
- module AbstractOutputStream
7
+ module AbstractOutputStream # :nodoc:
6
8
  include FakeIO
7
9
 
8
10
  def write(data)
@@ -11,11 +13,19 @@ module Zip
11
13
  end
12
14
 
13
15
  def print(*params)
14
- self << params.join($OUTPUT_FIELD_SEPARATOR) << $OUTPUT_RECORD_SEPARATOR.to_s
16
+ self << params.join
17
+
18
+ # Deflate doesn't like `nil`s or empty strings!
19
+ unless $OUTPUT_RECORD_SEPARATOR.nil? || $OUTPUT_RECORD_SEPARATOR.empty?
20
+ self << $OUTPUT_RECORD_SEPARATOR
21
+ end
22
+
23
+ nil
15
24
  end
16
25
 
17
26
  def printf(a_format_string, *params)
18
27
  self << format(a_format_string, *params)
28
+ nil
19
29
  end
20
30
 
21
31
  def putc(an_object)
data/lib/zip/ioextras.rb CHANGED
@@ -1,26 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- module IOExtras #:nodoc:
4
+ module IOExtras # :nodoc:
3
5
  CHUNK_SIZE = 131_072
4
6
 
5
- RANGE_ALL = 0..-1
6
-
7
7
  class << self
8
8
  def copy_stream(ostream, istream)
9
- ostream.write(istream.read(CHUNK_SIZE, ''.b)) until istream.eof?
9
+ ostream.write(istream.read(CHUNK_SIZE, +'')) until istream.eof?
10
10
  end
11
11
 
12
12
  def copy_stream_n(ostream, istream, nbytes)
13
13
  toread = nbytes
14
14
  while toread > 0 && !istream.eof?
15
- tr = toread > CHUNK_SIZE ? CHUNK_SIZE : toread
16
- ostream.write(istream.read(tr, ''.b))
15
+ tr = [toread, CHUNK_SIZE].min
16
+ ostream.write(istream.read(tr, +''))
17
17
  toread -= tr
18
18
  end
19
19
  end
20
20
  end
21
21
 
22
22
  # Implements kind_of? in order to pretend to be an IO object
23
- module FakeIO
23
+ module FakeIO # :nodoc:
24
24
  def kind_of?(object)
25
25
  object == IO || super
26
26
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class NullCompressor < Compressor #:nodoc:all
4
+ class NullCompressor < Compressor # :nodoc:all
3
5
  include Singleton
4
6
 
5
7
  def <<(_data)
@@ -1,16 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- module NullDecompressor #:nodoc:all
4
+ module NullDecompressor # :nodoc:all
3
5
  module_function
4
6
 
5
7
  def read(_length = nil, _outbuf = nil)
6
8
  nil
7
9
  end
8
10
 
9
- def eof
11
+ def eof?
10
12
  true
11
13
  end
12
14
 
13
- alias eof? eof
15
+ # Alias for compatibility. Remove for version 4.
16
+ alias eof eof?
14
17
  end
15
18
  end
16
19
 
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- module NullInputStream #:nodoc:all
4
+ module NullInputStream # :nodoc:all
3
5
  include ::Zip::NullDecompressor
4
6
  include ::Zip::IOExtras::AbstractInputStream
5
7
  end
@@ -1,3 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ ##
1
6
  module Zip
2
7
  # ZipOutputStream is the basic class for writing zip files. It is
3
8
  # possible to create a ZipOutputStream object directly, passing
@@ -16,57 +21,52 @@ module Zip
16
21
  #
17
22
  # java.util.zip.ZipOutputStream is the original inspiration for this
18
23
  # class.
19
-
20
24
  class OutputStream
25
+ extend Forwardable
21
26
  include ::Zip::IOExtras::AbstractOutputStream
22
27
 
23
- attr_accessor :comment
28
+ def_delegators :@cdir, :comment, :comment=
24
29
 
25
30
  # Opens the indicated zip file. If a file with that name already
26
31
  # exists it will be overwritten.
27
- def initialize(file_name, dep_stream = false, dep_encrypter = nil, stream: false, encrypter: nil)
32
+ def initialize(file_name, stream: false, encrypter: nil, suppress_extra_fields: false)
28
33
  super()
29
-
30
- Zip.warn_about_v3_api('Zip::OutputStream.new') if dep_stream || !dep_encrypter.nil?
31
-
32
34
  @file_name = file_name
33
- @output_stream = if stream || dep_stream
34
- iostream = @file_name.dup
35
+ @output_stream = if stream
36
+ iostream = Zip::RUNNING_ON_WINDOWS ? @file_name : @file_name.dup
35
37
  iostream.reopen(@file_name)
36
38
  iostream.rewind
37
39
  iostream
38
40
  else
39
41
  ::File.new(@file_name, 'wb')
40
42
  end
41
- @entry_set = ::Zip::EntrySet.new
43
+ @cdir = ::Zip::CentralDirectory.new
42
44
  @compressor = ::Zip::NullCompressor.instance
43
- @encrypter = encrypter || dep_encrypter || ::Zip::NullEncrypter.new
45
+ @encrypter = encrypter || ::Zip::NullEncrypter.new
46
+ @suppress_extra_fields = suppress_extra_fields
44
47
  @closed = false
45
48
  @current_entry = nil
46
- @comment = nil
47
49
  end
48
50
 
49
- # Same as #initialize but if a block is passed the opened
50
- # stream is passed to the block and closed when the block
51
- # returns.
52
51
  class << self
53
- def open(file_name, dep_encrypter = nil, encrypter: nil)
52
+ # Same as #initialize but if a block is passed the opened
53
+ # stream is passed to the block and closed when the block
54
+ # returns.
55
+ def open(file_name, encrypter: nil, suppress_extra_fields: false)
54
56
  return new(file_name) unless block_given?
55
57
 
56
- Zip.warn_about_v3_api('Zip::OutputStream.open') unless dep_encrypter.nil?
57
-
58
- zos = new(file_name, stream: false, encrypter: (encrypter || dep_encrypter))
58
+ zos = new(file_name, stream: false, encrypter: encrypter,
59
+ suppress_extra_fields: suppress_extra_fields)
59
60
  yield zos
60
61
  ensure
61
62
  zos.close if zos
62
63
  end
63
64
 
64
65
  # Same as #open but writes to a filestream instead
65
- def write_buffer(io = ::StringIO.new, dep_encrypter = nil, encrypter: nil)
66
- Zip.warn_about_v3_api('Zip::OutputStream.write_buffer') unless dep_encrypter.nil?
67
-
66
+ def write_buffer(io = ::StringIO.new, encrypter: nil, suppress_extra_fields: false)
68
67
  io.binmode if io.respond_to?(:binmode)
69
- zos = new(io, stream: true, encrypter: (encrypter || dep_encrypter))
68
+ zos = new(io, stream: true, encrypter: encrypter,
69
+ suppress_extra_fields: suppress_extra_fields)
70
70
  yield zos
71
71
  zos.close_buffer
72
72
  end
@@ -78,7 +78,7 @@ module Zip
78
78
 
79
79
  finalize_current_entry
80
80
  update_local_headers
81
- write_central_directory
81
+ @cdir.write_to_stream(@output_stream, suppress_extra_fields: @suppress_extra_fields)
82
82
  @output_stream.close
83
83
  @closed = true
84
84
  end
@@ -89,37 +89,41 @@ module Zip
89
89
 
90
90
  finalize_current_entry
91
91
  update_local_headers
92
- write_central_directory
92
+ @cdir.write_to_stream(@output_stream, suppress_extra_fields: @suppress_extra_fields)
93
93
  @closed = true
94
+ @output_stream.flush
94
95
  @output_stream
95
96
  end
96
97
 
97
98
  # Closes the current entry and opens a new for writing.
98
99
  # +entry+ can be a ZipEntry object or a string.
99
- def put_next_entry(entry_name, comment = nil, extra = nil, compression_method = Entry::DEFLATED, level = Zip.default_compression)
100
+ def put_next_entry(
101
+ entry_name, comment = '', extra = ExtraField.new,
102
+ compression_method = Entry::DEFLATED, level = Zip.default_compression
103
+ )
100
104
  raise Error, 'zip stream is closed' if @closed
101
105
 
102
- new_entry = if entry_name.kind_of?(Entry)
103
- entry_name
104
- else
105
- Entry.new(@file_name, entry_name.to_s)
106
- end
107
- new_entry.comment = comment unless comment.nil?
108
- unless extra.nil?
109
- new_entry.extra = extra.kind_of?(ExtraField) ? extra : ExtraField.new(extra.to_s)
110
- end
111
- new_entry.compression_method = compression_method unless compression_method.nil?
112
- init_next_entry(new_entry, level)
106
+ new_entry =
107
+ if entry_name.kind_of?(Entry) || entry_name.kind_of?(StreamableStream)
108
+ entry_name
109
+ else
110
+ Entry.new(
111
+ @file_name, entry_name.to_s, comment: comment, extra: extra,
112
+ compression_method: compression_method, compression_level: level
113
+ )
114
+ end
115
+
116
+ init_next_entry(new_entry)
113
117
  @current_entry = new_entry
114
118
  end
115
119
 
116
- def copy_raw_entry(entry)
120
+ def copy_raw_entry(entry) # :nodoc:
117
121
  entry = entry.dup
118
122
  raise Error, 'zip stream is closed' if @closed
119
123
  raise Error, 'entry is not a ZipEntry' unless entry.kind_of?(Entry)
120
124
 
121
125
  finalize_current_entry
122
- @entry_set << entry
126
+ @cdir << entry
123
127
  src_pos = entry.local_header_offset
124
128
  entry.write_local_entry(@output_stream)
125
129
  @compressor = NullCompressor.instance
@@ -138,55 +142,54 @@ module Zip
138
142
  return unless @current_entry
139
143
 
140
144
  finish
141
- @current_entry.compressed_size = @output_stream.tell - \
142
- @current_entry.local_header_offset - \
145
+ @current_entry.compressed_size = @output_stream.tell -
146
+ @current_entry.local_header_offset -
143
147
  @current_entry.calculate_local_header_size
144
148
  @current_entry.size = @compressor.size
145
149
  @current_entry.crc = @compressor.crc
146
- @output_stream << @encrypter.data_descriptor(@current_entry.crc, @current_entry.compressed_size, @current_entry.size)
150
+ @output_stream << @encrypter.data_descriptor(
151
+ @current_entry.crc,
152
+ @current_entry.compressed_size,
153
+ @current_entry.size
154
+ )
147
155
  @current_entry.gp_flags |= @encrypter.gp_flags
148
156
  @current_entry = nil
149
157
  @compressor = ::Zip::NullCompressor.instance
150
158
  end
151
159
 
152
- def init_next_entry(entry, level = Zip.default_compression)
160
+ def init_next_entry(entry)
153
161
  finalize_current_entry
154
- @entry_set << entry
155
- entry.write_local_entry(@output_stream)
162
+ @cdir << entry
163
+ entry.write_local_entry(@output_stream, suppress_extra_fields: @suppress_extra_fields)
156
164
  @encrypter.reset!
157
165
  @output_stream << @encrypter.header(entry.mtime)
158
- @compressor = get_compressor(entry, level)
166
+ @compressor = get_compressor(entry)
159
167
  end
160
168
 
161
- def get_compressor(entry, level)
169
+ def get_compressor(entry)
162
170
  case entry.compression_method
163
171
  when Entry::DEFLATED
164
- ::Zip::Deflater.new(@output_stream, level, @encrypter)
172
+ ::Zip::Deflater.new(@output_stream, entry.compression_level, @encrypter)
165
173
  when Entry::STORED
166
174
  ::Zip::PassThruCompressor.new(@output_stream)
167
175
  else
168
- raise ::Zip::CompressionMethodError,
169
- "Invalid compression method: '#{entry.compression_method}'"
176
+ raise ::Zip::CompressionMethodError, entry.compression_method
170
177
  end
171
178
  end
172
179
 
173
180
  def update_local_headers
174
181
  pos = @output_stream.pos
175
- @entry_set.each do |entry|
182
+ @cdir.each do |entry|
176
183
  @output_stream.pos = entry.local_header_offset
177
- entry.write_local_entry(@output_stream, true)
184
+ entry.write_local_entry(@output_stream, suppress_extra_fields: @suppress_extra_fields,
185
+ rewrite: true)
178
186
  end
179
187
  @output_stream.pos = pos
180
188
  end
181
189
 
182
- def write_central_directory
183
- cdir = CentralDirectory.new(@entry_set, @comment)
184
- cdir.write_to_stream(@output_stream)
185
- end
186
-
187
190
  protected
188
191
 
189
- def finish
192
+ def finish # :nodoc:
190
193
  @compressor.finish
191
194
  end
192
195
 
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class PassThruCompressor < Compressor #:nodoc:all
4
+ class PassThruCompressor < Compressor # :nodoc:all
3
5
  def initialize(output_stream)
4
6
  super()
5
7
  @output_stream = output_stream