rubyzip 2.4.1 → 3.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +495 -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 +179 -125
  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 +21 -15
  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 +392 -266
  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 +13 -10
  39. data/lib/zip/input_stream.rb +98 -60
  40. data/lib/zip/ioextras/abstract_input_stream.rb +146 -58
  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 +12 -9
  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
 
@@ -86,23 +89,43 @@ module Zip
86
89
  open_entry
87
90
  end
88
91
 
89
- # Modeled after IO.sysread
90
- def sysread(length = nil, outbuf = '')
91
- @decompressor.read(length, outbuf)
92
+ # Modelled after IO#sysread.
93
+ #
94
+ # Reads up to maxlen bytes from the stream; returns a string
95
+ # (either a new string or the given out_string).
96
+ # Its encoding is the unchanged encoding of out_string, if out_string is
97
+ # given; ASCII-8BIT, otherwise. Output contains maxlen bytes from the
98
+ # stream, if available; otherwise contains all available bytes, if any
99
+ # available; otherwise is an empty string.
100
+ #
101
+ # This method should not be used with buffered input stream-reader methods,
102
+ # such as #read, #readline, #gets.
103
+ def sysread(maxlen, out_string = nil)
104
+ return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
105
+
106
+ output = produce_input(maxlen)
107
+
108
+ if out_string.nil?
109
+ output.force_encoding(Encoding::ASCII_8BIT)
110
+ else
111
+ encoding = out_string.encoding
112
+ out_string.replace(output).force_encoding(encoding)
113
+ end
114
+ end
115
+
116
+ # Returns the size of the current entry, or `nil` if there isn't one.
117
+ def size
118
+ return if @current_entry.nil?
119
+
120
+ @current_entry.size
92
121
  end
93
122
 
94
123
  class << self
95
124
  # Same as #initialize but if a block is passed the opened
96
125
  # stream is passed to the block and closed when the block
97
126
  # 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)
127
+ def open(filename_or_io, offset: 0, decrypter: nil)
128
+ zio = new(filename_or_io, offset: offset, decrypter: decrypter)
106
129
  return zio unless block_given?
107
130
 
108
131
  begin
@@ -111,17 +134,11 @@ module Zip
111
134
  zio.close if zio
112
135
  end
113
136
  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
137
  end
121
138
 
122
139
  protected
123
140
 
124
- def get_io(io_or_file, offset = 0)
141
+ def get_io(io_or_file, offset = 0) # :nodoc:
125
142
  if io_or_file.respond_to?(:seek)
126
143
  io = io_or_file.dup
127
144
  io.seek(offset, ::IO::SEEK_SET)
@@ -133,57 +150,78 @@ module Zip
133
150
  end
134
151
  end
135
152
 
136
- def open_entry
153
+ def open_entry # :nodoc:
137
154
  @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
155
+ return if @current_entry.nil?
141
156
 
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'
157
+ if @current_entry.incomplete? && @current_entry.compressed_size == 0 && !@complete_entry
158
+ raise StreamingError, @current_entry
148
159
  end
149
- @decrypted_io = get_decrypted_io
150
- @decompressor = get_decompressor
160
+
161
+ @decompressor = assemble_io
151
162
  flush
152
163
  @current_entry
153
164
  end
154
165
 
155
- def get_decrypted_io
166
+ def assemble_io # :nodoc:
167
+ io = if @current_entry.encrypted?
168
+ raise Error, 'A password is required to decode this zip file.' if @decrypter.nil?
169
+
170
+ get_decrypted_io
171
+ else
172
+ @archive_io
173
+ end
174
+
175
+ get_decompressor(io)
176
+ end
177
+
178
+ def get_decrypted_io # :nodoc:
156
179
  header = @archive_io.read(@decrypter.header_bytesize)
157
180
  @decrypter.reset!(header)
158
181
 
159
- ::Zip::DecryptedIo.new(@archive_io, @decrypter)
182
+ compressed_size =
183
+ if @current_entry.incomplete? && @current_entry.crc == 0 &&
184
+ @current_entry.compressed_size == 0 && @complete_entry
185
+ @complete_entry.compressed_size
186
+ else
187
+ @current_entry.compressed_size
188
+ end
189
+
190
+ if @decrypter.kind_of?(::Zip::AESDecrypter)
191
+ compressed_size -= @decrypter.header_bytesize
192
+ compressed_size -= ::Zip::AESEncryption::AUTHENTICATION_CODE_LENGTH
193
+ end
194
+
195
+ ::Zip::DecryptedIo.new(@archive_io, @decrypter, compressed_size)
160
196
  end
161
197
 
162
- def get_decompressor
198
+ def get_decompressor(io) # :nodoc:
163
199
  return ::Zip::NullDecompressor if @current_entry.nil?
164
200
 
165
201
  decompressed_size =
166
- if @current_entry.incomplete? && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry
202
+ if @current_entry.incomplete? && @current_entry.crc == 0 &&
203
+ @current_entry.size == 0 && @complete_entry
167
204
  @complete_entry.size
168
205
  else
169
206
  @current_entry.size
170
207
  end
171
208
 
172
- decompressor_class = ::Zip::Decompressor.find_by_compression_method(@current_entry.compression_method)
209
+ decompressor_class = ::Zip::Decompressor.find_by_compression_method(
210
+ @current_entry.compression_method
211
+ )
173
212
  if decompressor_class.nil?
174
- raise ::Zip::CompressionMethodError,
175
- "Unsupported compression method #{@current_entry.compression_method}"
213
+ raise ::Zip::CompressionMethodError, @current_entry.compression_method
176
214
  end
177
215
 
178
- decompressor_class.new(@decrypted_io, decompressed_size)
216
+ decompressor_class.new(io, decompressed_size)
179
217
  end
180
218
 
181
- def produce_input
182
- @decompressor.read(CHUNK_SIZE)
219
+ def produce_input(maxlen = CHUNK_SIZE) # :nodoc:
220
+ @decompressor.read(maxlen)
183
221
  end
184
222
 
185
- def input_finished?
186
- @decompressor.eof
223
+ def input_finished? # :nodoc:
224
+ @decompressor.eof?
187
225
  end
188
226
  end
189
227
  end
@@ -1,122 +1,210 @@
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
- # such as gets, getc, readline and readlines
6
+ # such as gets, getc, read, readline and readlines
5
7
  # depends on: input_finished?, produce_input and read
6
8
  module AbstractInputStream
7
9
  include Enumerable
8
10
  include FakeIO
9
11
 
10
- def initialize
12
+ def initialize # :nodoc:
11
13
  super
12
14
  @lineno = 0
13
15
  @pos = 0
14
- @output_buffer = ''.b
16
+ @output_buffer = +''.b
15
17
  end
16
18
 
19
+ # Returns (or sets) the current line number in the decompressed
20
+ # (possibly decrypted) data stream. See the Line Number documentation
21
+ # for the IO class for more information.
17
22
  attr_accessor :lineno
23
+
24
+ # Returns the current position (in bytes) in the decompressed (possibly
25
+ # decrypted) data stream.
18
26
  attr_reader :pos
19
27
 
20
- def read(number_of_bytes = nil, buf = ''.b)
28
+ # Reads bytes from the stream decompressed (possibly decrypted) data
29
+ # stream. If `maxlen` is `nil`, reads all bytes; otherwise, reads up to
30
+ # `maxlen` bytes. If `maxlen` is zero, returns an empty string.
31
+ #
32
+ # Returns a string (either a new string or the given `out_string`)
33
+ # containing the bytes read. The string's encoding is the unchanged
34
+ # encoding of `out_string`, if `out_string` is given; `ASCII-8BIT`,
35
+ # otherwise.
36
+ def read(maxlen = nil, out_string = nil) # rubocop:disable Metrics/PerceivedComplexity
37
+ return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
38
+
21
39
  tbuf = if @output_buffer.bytesize > 0
22
- if number_of_bytes <= @output_buffer.bytesize
23
- @output_buffer.slice!(0, number_of_bytes)
40
+ if maxlen && maxlen <= @output_buffer.bytesize
41
+ @output_buffer.slice!(0, maxlen)
24
42
  else
25
- number_of_bytes -= @output_buffer.bytesize if number_of_bytes
26
- rbuf = sysread(number_of_bytes, buf)
43
+ maxlen -= @output_buffer.bytesize if maxlen
44
+ rbuf = produce_input(maxlen)
27
45
  out = @output_buffer
28
46
  out << rbuf if rbuf
29
- @output_buffer = ''.b
47
+ @output_buffer = +''.b
30
48
  out
31
49
  end
32
50
  else
33
- sysread(number_of_bytes, buf)
51
+ produce_input(maxlen)
34
52
  end
35
53
 
36
54
  if tbuf.nil? || tbuf.empty?
37
- return nil if number_of_bytes
55
+ return nil if maxlen&.positive?
38
56
 
39
57
  return ''
40
58
  end
41
59
 
42
60
  @pos += tbuf.length
43
61
 
44
- if buf
45
- buf.replace(tbuf)
62
+ if out_string.nil?
63
+ tbuf.force_encoding(Encoding::ASCII_8BIT)
46
64
  else
47
- buf = tbuf
65
+ encoding = out_string.encoding
66
+ out_string.replace(tbuf).force_encoding(encoding)
48
67
  end
49
- buf
50
68
  end
51
69
 
52
- def readlines(a_sep_string = $INPUT_RECORD_SEPARATOR)
53
- ret_val = []
54
- each_line(a_sep_string) { |line| ret_val << line }
55
- ret_val
70
+ # Reads and returns all remaining lines from the stream. See the Line IO
71
+ # documentation in the IO class for more information.
72
+ #
73
+ # With no arguments given, returns lines as determined by line
74
+ # separator `$/`, or `nil` if none.
75
+ #
76
+ # With only string argument `sep` given, returns lines as
77
+ # determined by line separator `sep`, or `nil` if none. See the
78
+ # Line Separator documentation in the IO class for more information.
79
+ # The two special values for `sep` (`nil` and `""`) are honoured.
80
+ #
81
+ # With only integer argument `limit` given, limits the number of bytes
82
+ # in each line; see the Line Limit documentation in the IO class for more
83
+ # information.
84
+ #
85
+ # With arguments `sep` and `limit` given, combines the two behaviors.
86
+ #
87
+ # Optional keyword argument `chomp` specifies whether line separators
88
+ # are to be omitted.
89
+ def readlines(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
90
+ each(sep, limit, chomp: chomp).to_a
56
91
  end
57
92
 
58
- def gets(a_sep_string = $INPUT_RECORD_SEPARATOR, number_of_bytes = nil)
59
- @lineno = @lineno.next
93
+ # Reads and returns a line from the stream. See the Line IO
94
+ # documentation in the IO class for more information.
95
+ #
96
+ # With no arguments given, returns the next line as determined by line
97
+ # separator `$/`, or `nil` if none.
98
+ #
99
+ # With only string argument `sep` given, returns the next line as
100
+ # determined by line separator `sep`, or `nil` if none. See the
101
+ # Line Separator documentation in the IO class for more information.
102
+ # The two special values for `sep` (`nil` and `""`) are honoured.
103
+ #
104
+ # With only integer argument `limit` given, limits the number of bytes
105
+ # in the line; see the Line Limit documentation in the IO class for more
106
+ # information.
107
+ #
108
+ # With arguments `sep` and `limit` given, combines the two behaviors.
109
+ #
110
+ # Optional keyword argument `chomp` specifies whether line separators
111
+ # are to be omitted.
112
+ def gets(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
113
+ if sep.nil?
114
+ return nil if eof?
115
+
116
+ @lineno = @lineno.next
117
+ return read(limit)
118
+ end
60
119
 
61
- if number_of_bytes.respond_to?(:to_int)
62
- number_of_bytes = number_of_bytes.to_int
63
- a_sep_string = a_sep_string.to_str if a_sep_string
64
- elsif a_sep_string.respond_to?(:to_int)
65
- number_of_bytes = a_sep_string.to_int
66
- a_sep_string = $INPUT_RECORD_SEPARATOR
67
- else
68
- number_of_bytes = nil
69
- a_sep_string = a_sep_string.to_str if a_sep_string
120
+ if sep.respond_to?(:to_int)
121
+ limit = sep.to_int
122
+ sep = $INPUT_RECORD_SEPARATOR
123
+ elsif sep&.empty?
124
+ sep = "#{$INPUT_RECORD_SEPARATOR}#{$INPUT_RECORD_SEPARATOR}"
70
125
  end
71
126
 
72
- return read(number_of_bytes) if a_sep_string.nil?
127
+ buffer_index = 0
128
+ while (sep_index = @output_buffer.index(sep, buffer_index)).nil?
129
+ break if limit && @output_buffer.bytesize >= limit
73
130
 
74
- a_sep_string = "#{$INPUT_RECORD_SEPARATOR}#{$INPUT_RECORD_SEPARATOR}" if a_sep_string.empty?
131
+ if input_finished?
132
+ return nil if @output_buffer.empty?
75
133
 
76
- buffer_index = 0
77
- over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes)
78
- while (match_index = @output_buffer.index(a_sep_string, buffer_index)).nil? && !over_limit
79
- buffer_index = [buffer_index, @output_buffer.bytesize - a_sep_string.bytesize].max
80
- return @output_buffer.empty? ? nil : flush if input_finished?
134
+ @lineno = @lineno.next
135
+ @pos += @output_buffer.bytesize
136
+ return @output_buffer.slice!(0..)
137
+ end
81
138
 
139
+ buffer_index = [buffer_index, @output_buffer.bytesize - sep.bytesize].max
82
140
  @output_buffer << produce_input
83
- over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes)
84
141
  end
85
- sep_index = [match_index + a_sep_string.bytesize, number_of_bytes || @output_buffer.bytesize].min
86
- @pos += sep_index
87
- @output_buffer.slice!(0...sep_index)
142
+
143
+ limit ||= @output_buffer.bytesize
144
+ cut_index = sep_index ? [sep_index + sep.bytesize, limit].min : limit
145
+ @lineno = @lineno.next
146
+ @pos += cut_index
147
+ chomp ? @output_buffer.slice!(0, cut_index).chomp(sep) : @output_buffer.slice!(0, cut_index)
88
148
  end
89
149
 
90
- def ungetc(byte)
150
+ def ungetc(byte) # :nodoc:
91
151
  @output_buffer = byte.chr + @output_buffer
92
152
  end
93
153
 
94
- def flush
95
- ret_val = @output_buffer
96
- @output_buffer = ''.b
97
- ret_val
154
+ def flush # :nodoc:
155
+ @output_buffer.slice!(0..)
98
156
  end
99
157
 
100
- def readline(a_sep_string = $INPUT_RECORD_SEPARATOR)
101
- ret_val = gets(a_sep_string)
102
- raise EOFError unless ret_val
158
+ # Reads a line as with #gets, but raises `EOFError` if already at
159
+ # end-of-stream.
160
+ #
161
+ # Optional keyword argument `chomp` specifies whether line separators
162
+ # are to be omitted.
163
+ def readline(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
164
+ raise EOFError if eof?
103
165
 
104
- ret_val
166
+ gets(sep, limit, chomp: chomp)
105
167
  end
106
168
 
107
- def each_line(a_sep_string = $INPUT_RECORD_SEPARATOR)
108
- loop { yield readline(a_sep_string) }
109
- rescue EOFError
110
- # We just need to catch this; we don't need to handle it.
169
+ # Calls the block with each remaining line read from the stream.
170
+ # Does nothing if already at end-of-stream. See the Line IO
171
+ # documentation in the IO class for more information.
172
+ #
173
+ # With no arguments given, reads lines as determined by line separator
174
+ # `$/`. With only string argument `sep` given, reads lines as determined
175
+ # by line separator `sep`. See the Line Separator documentation in the
176
+ # IO class for more information. The two special values for `sep`
177
+ # (`nil` and `""`) are honoured.
178
+ #
179
+ # With only integer argument `limit` given, limits the number of bytes
180
+ # in each line; see the Line Limit documentation in the IO class for
181
+ # more information.
182
+ #
183
+ # With arguments `sep` and `limit` given, combines the two behaviors.
184
+ #
185
+ # Optional keyword argument `chomp` specifies whether line separators
186
+ # are to be omitted.
187
+ #
188
+ # Returns an `Enumerator` if no block is given.
189
+ def each(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
190
+ return to_enum(:each, sep, limit, chomp: chomp) unless block_given?
191
+
192
+ while (line = gets(sep, limit, chomp: chomp))
193
+ yield line
194
+ end
111
195
  end
112
196
 
113
- alias each each_line
197
+ alias each_line each
114
198
 
115
- def eof
199
+ # Returns `true` if the stream is positioned at its end, `false`
200
+ # otherwise. See Position documentation in the IO class for more
201
+ # information.
202
+ def eof?
116
203
  @output_buffer.empty? && input_finished?
117
204
  end
118
205
 
119
- alias eof? eof
206
+ # Alias for compatibility. Remove for version 4.
207
+ alias eof eof? # :nodoc:
120
208
  end
121
209
  end
122
210
  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