rubyzip 1.3.0 → 3.4.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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +516 -0
  3. data/LICENSE.md +24 -0
  4. data/README.md +213 -51
  5. data/Rakefile +17 -12
  6. data/lib/rubyzip.rb +3 -0
  7. data/lib/zip/central_directory.rb +196 -123
  8. data/lib/zip/compressor.rb +3 -1
  9. data/lib/zip/constants.rb +81 -21
  10. data/lib/zip/crypto/aes_encryption.rb +120 -0
  11. data/lib/zip/crypto/decrypted_io.rb +46 -0
  12. data/lib/zip/crypto/encryption.rb +4 -2
  13. data/lib/zip/crypto/null_encryption.rb +5 -13
  14. data/lib/zip/crypto/traditional_encryption.rb +22 -16
  15. data/lib/zip/decompressor.rb +21 -2
  16. data/lib/zip/deflater.rb +12 -8
  17. data/lib/zip/dirtyable.rb +32 -0
  18. data/lib/zip/dos_time.rb +65 -13
  19. data/lib/zip/entry.rb +433 -249
  20. data/lib/zip/entry_set.rb +13 -9
  21. data/lib/zip/errors.rb +152 -15
  22. data/lib/zip/extra_field/aes.rb +50 -0
  23. data/lib/zip/extra_field/generic.rb +18 -18
  24. data/lib/zip/extra_field/ntfs.rb +10 -4
  25. data/lib/zip/extra_field/old_unix.rb +6 -2
  26. data/lib/zip/extra_field/universal_time.rb +45 -13
  27. data/lib/zip/extra_field/unix.rb +7 -3
  28. data/lib/zip/extra_field/unknown.rb +35 -0
  29. data/lib/zip/extra_field/zip64.rb +23 -7
  30. data/lib/zip/extra_field.rb +31 -27
  31. data/lib/zip/file.rb +225 -241
  32. data/lib/zip/file_split.rb +91 -0
  33. data/lib/zip/filesystem/dir.rb +86 -0
  34. data/lib/zip/filesystem/directory_iterator.rb +48 -0
  35. data/lib/zip/filesystem/file.rb +263 -0
  36. data/lib/zip/filesystem/file_stat.rb +110 -0
  37. data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
  38. data/lib/zip/filesystem.rb +32 -585
  39. data/lib/zip/inflater.rb +28 -37
  40. data/lib/zip/input_stream.rb +113 -54
  41. data/lib/zip/ioextras/abstract_input_stream.rb +154 -55
  42. data/lib/zip/ioextras/abstract_output_stream.rb +13 -3
  43. data/lib/zip/ioextras.rb +8 -8
  44. data/lib/zip/null_compressor.rb +3 -1
  45. data/lib/zip/null_decompressor.rb +7 -12
  46. data/lib/zip/null_input_stream.rb +3 -1
  47. data/lib/zip/output_stream.rb +69 -50
  48. data/lib/zip/pass_thru_compressor.rb +5 -3
  49. data/lib/zip/pass_thru_decompressor.rb +17 -23
  50. data/lib/zip/streamable_directory.rb +6 -4
  51. data/lib/zip/streamable_stream.rb +10 -11
  52. data/lib/zip/version.rb +4 -1
  53. data/lib/zip.rb +32 -7
  54. data/rubyzip.gemspec +39 -0
  55. data/samples/example.rb +9 -4
  56. data/samples/example_filesystem.rb +4 -3
  57. data/samples/example_recursive.rb +3 -1
  58. data/samples/gtk_ruby_zip.rb +23 -21
  59. data/samples/qtzip.rb +13 -12
  60. data/samples/write_simple.rb +3 -4
  61. data/samples/zipfind.rb +24 -22
  62. metadata +85 -177
  63. data/TODO +0 -15
  64. data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
  65. data/test/basic_zip_file_test.rb +0 -60
  66. data/test/case_sensitivity_test.rb +0 -69
  67. data/test/central_directory_entry_test.rb +0 -69
  68. data/test/central_directory_test.rb +0 -100
  69. data/test/crypto/null_encryption_test.rb +0 -57
  70. data/test/crypto/traditional_encryption_test.rb +0 -80
  71. data/test/data/WarnInvalidDate.zip +0 -0
  72. data/test/data/file1.txt +0 -46
  73. data/test/data/file1.txt.deflatedData +0 -0
  74. data/test/data/file2.txt +0 -1504
  75. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  76. data/test/data/globTest/foo.txt +0 -0
  77. data/test/data/globTest/food.txt +0 -0
  78. data/test/data/globTest.zip +0 -0
  79. data/test/data/gpbit3stored.zip +0 -0
  80. data/test/data/mimetype +0 -1
  81. data/test/data/notzippedruby.rb +0 -7
  82. data/test/data/ntfs.zip +0 -0
  83. data/test/data/oddExtraField.zip +0 -0
  84. data/test/data/path_traversal/Makefile +0 -10
  85. data/test/data/path_traversal/jwilk/README.md +0 -5
  86. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  87. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  88. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  89. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  90. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  91. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  92. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  93. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  94. data/test/data/path_traversal/relative1.zip +0 -0
  95. data/test/data/path_traversal/tilde.zip +0 -0
  96. data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
  97. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  98. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  99. data/test/data/rubycode.zip +0 -0
  100. data/test/data/rubycode2.zip +0 -0
  101. data/test/data/test.xls +0 -0
  102. data/test/data/testDirectory.bin +0 -0
  103. data/test/data/zip64-sample.zip +0 -0
  104. data/test/data/zipWithDirs.zip +0 -0
  105. data/test/data/zipWithEncryption.zip +0 -0
  106. data/test/deflater_test.rb +0 -65
  107. data/test/encryption_test.rb +0 -42
  108. data/test/entry_set_test.rb +0 -163
  109. data/test/entry_test.rb +0 -154
  110. data/test/errors_test.rb +0 -35
  111. data/test/extra_field_test.rb +0 -76
  112. data/test/file_extract_directory_test.rb +0 -54
  113. data/test/file_extract_test.rb +0 -145
  114. data/test/file_permissions_test.rb +0 -65
  115. data/test/file_split_test.rb +0 -57
  116. data/test/file_test.rb +0 -666
  117. data/test/filesystem/dir_iterator_test.rb +0 -58
  118. data/test/filesystem/directory_test.rb +0 -139
  119. data/test/filesystem/file_mutating_test.rb +0 -87
  120. data/test/filesystem/file_nonmutating_test.rb +0 -508
  121. data/test/filesystem/file_stat_test.rb +0 -64
  122. data/test/gentestfiles.rb +0 -126
  123. data/test/inflater_test.rb +0 -14
  124. data/test/input_stream_test.rb +0 -182
  125. data/test/ioextras/abstract_input_stream_test.rb +0 -102
  126. data/test/ioextras/abstract_output_stream_test.rb +0 -106
  127. data/test/ioextras/fake_io_test.rb +0 -18
  128. data/test/local_entry_test.rb +0 -154
  129. data/test/output_stream_test.rb +0 -128
  130. data/test/pass_thru_compressor_test.rb +0 -30
  131. data/test/pass_thru_decompressor_test.rb +0 -14
  132. data/test/path_traversal_test.rb +0 -141
  133. data/test/samples/example_recursive_test.rb +0 -37
  134. data/test/settings_test.rb +0 -95
  135. data/test/test_helper.rb +0 -234
  136. data/test/unicode_file_names_and_comments_test.rb +0 -62
  137. data/test/zip64_full_test.rb +0 -51
  138. data/test/zip64_support_test.rb +0 -14
data/lib/zip/inflater.rb CHANGED
@@ -1,64 +1,55 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class Inflater < Decompressor #:nodoc:all
3
- def initialize(input_stream, decrypter = NullDecrypter.new)
4
- super(input_stream)
5
- @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS)
6
- @output_buffer = ''.dup
7
- @has_returned_empty_string = false
8
- @decrypter = decrypter
9
- end
4
+ class Inflater < Decompressor # :nodoc:all
5
+ def initialize(*args)
6
+ super
10
7
 
11
- def sysread(number_of_bytes = nil, buf = '')
12
- readEverything = number_of_bytes.nil?
13
- while readEverything || @output_buffer.bytesize < number_of_bytes
14
- break if internal_input_finished?
15
- @output_buffer << internal_produce_input(buf)
16
- end
17
- return value_when_finished if @output_buffer.bytesize == 0 && input_finished?
18
- end_index = number_of_bytes.nil? ? @output_buffer.bytesize : number_of_bytes
19
- @output_buffer.slice!(0...end_index)
8
+ @buffer = +''.b
9
+ @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS)
20
10
  end
21
11
 
22
- def produce_input
23
- if @output_buffer.empty?
24
- internal_produce_input
25
- else
26
- @output_buffer.slice!(0...(@output_buffer.length))
12
+ def read(maxlen = nil)
13
+ return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
14
+
15
+ while maxlen.nil? || (@buffer.bytesize < maxlen)
16
+ break if input_finished?
17
+
18
+ @buffer << produce_input
27
19
  end
20
+
21
+ @buffer.slice!(0...(maxlen || @buffer.bytesize))
28
22
  end
29
23
 
30
- # to be used with produce_input, not read (as read may still have more data cached)
31
- # is data cached anywhere other than @outputBuffer? the comment above may be wrong
32
- def input_finished?
33
- @output_buffer.empty? && internal_input_finished?
24
+ def eof?
25
+ @buffer.empty? && input_finished?
34
26
  end
35
27
 
36
- alias :eof input_finished?
37
- alias :eof? input_finished?
28
+ # Alias for compatibility. Remove for version 4.
29
+ alias eof eof?
38
30
 
39
31
  private
40
32
 
41
- def internal_produce_input(buf = '')
33
+ def produce_input
42
34
  retried = 0
43
35
  begin
44
- @zlib_inflater.inflate(@decrypter.decrypt(@input_stream.read(Decompressor::CHUNK_SIZE, buf)))
36
+ @zlib_inflater.inflate(input_stream.read(Decompressor::CHUNK_SIZE))
45
37
  rescue Zlib::BufError
46
38
  raise if retried >= 5 # how many times should we retry?
39
+
47
40
  retried += 1
48
41
  retry
49
42
  end
43
+ rescue Zlib::Error => e
44
+ raise ::Zip::DecompressionError, e
50
45
  end
51
46
 
52
- def internal_input_finished?
47
+ def input_finished?
53
48
  @zlib_inflater.finished?
54
49
  end
55
-
56
- def value_when_finished # mimic behaviour of ruby File object.
57
- return if @has_returned_empty_string
58
- @has_returned_empty_string = true
59
- ''
60
- end
61
50
  end
51
+
52
+ ::Zip::Decompressor.register(::Zip::COMPRESSION_METHOD_DEFLATE, ::Zip::Inflater)
62
53
  end
63
54
 
64
55
  # Copyright (C) 2002, 2003 Thomas Sondergaard
@@ -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,8 +40,9 @@ module Zip
37
40
  #
38
41
  # java.util.zip.ZipInputStream is the original inspiration for this
39
42
  # class.
40
-
41
43
  class InputStream
44
+ CHUNK_SIZE = 32_768 # :nodoc:
45
+
42
46
  include ::Zip::IOExtras::AbstractInputStream
43
47
 
44
48
  # Opens the indicated zip file. An exception is thrown
@@ -47,70 +51,95 @@ module Zip
47
51
  #
48
52
  # @param context [String||IO||StringIO] file path or IO/StringIO object
49
53
  # @param offset [Integer] offset in the IO/StringIO
50
- def initialize(context, offset = 0, decrypter = nil)
54
+ def initialize(context, offset: 0, decrypter: nil)
51
55
  super()
52
56
  @archive_io = get_io(context, offset)
53
- @decompressor = ::Zip::NullDecompressor
54
- @decrypter = decrypter || ::Zip::NullDecrypter.new
57
+ @decompressor = ::Zip::NullDecompressor
58
+ @decrypter = decrypter
55
59
  @current_entry = nil
60
+ @complete_entry = nil
56
61
  end
57
62
 
63
+ # Close this InputStream. All further IO will raise an IOError.
58
64
  def close
59
65
  @archive_io.close
60
66
  end
61
67
 
62
- # Returns a Entry object. It is necessary to call this
63
- # method on a newly created InputStream before reading from
64
- # the first entry in the archive. Returns nil when there are
65
- # 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.
66
72
  def get_next_entry
67
- @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
+
68
79
  open_entry
69
80
  end
70
81
 
71
- # Rewinds the stream to the beginning of the current entry
82
+ # Rewinds the stream to the beginning of the current entry.
72
83
  def rewind
73
84
  return if @current_entry.nil?
85
+
74
86
  @lineno = 0
75
87
  @pos = 0
76
88
  @archive_io.seek(@current_entry.local_header_offset, IO::SEEK_SET)
77
89
  open_entry
78
90
  end
79
91
 
80
- # Modeled after IO.sysread
81
- def sysread(number_of_bytes = nil, buf = nil)
82
- @decompressor.sysread(number_of_bytes, buf)
83
- end
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 = nil, out_string = nil)
104
+ # Remove the default value for maxlen for version 4.
105
+ return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
84
106
 
85
- def eof
86
- @output_buffer.empty? && @decompressor.eof
107
+ output = produce_input(maxlen)
108
+
109
+ if out_string.nil?
110
+ output.force_encoding(Encoding::ASCII_8BIT)
111
+ else
112
+ encoding = out_string.encoding
113
+ out_string.replace(output).force_encoding(encoding)
114
+ end
87
115
  end
88
116
 
89
- alias :eof? eof
117
+ # Returns the size of the current entry, or `nil` if there isn't one.
118
+ def size
119
+ return if @current_entry.nil?
120
+
121
+ @current_entry.size
122
+ end
90
123
 
91
124
  class << self
92
125
  # Same as #initialize but if a block is passed the opened
93
126
  # stream is passed to the block and closed when the block
94
127
  # returns.
95
- def open(filename_or_io, offset = 0, decrypter = nil)
96
- zio = new(filename_or_io, offset, decrypter)
128
+ def open(filename_or_io, offset: 0, decrypter: nil)
129
+ zio = new(filename_or_io, offset: offset, decrypter: decrypter)
97
130
  return zio unless block_given?
131
+
98
132
  begin
99
133
  yield zio
100
134
  ensure
101
135
  zio.close if zio
102
136
  end
103
137
  end
104
-
105
- def open_buffer(filename_or_io, offset = 0)
106
- puts 'open_buffer is deprecated!!! Use open instead!'
107
- open(filename_or_io, offset)
108
- end
109
138
  end
110
139
 
111
140
  protected
112
141
 
113
- def get_io(io_or_file, offset = 0)
142
+ def get_io(io_or_file, offset = 0) # :nodoc:
114
143
  if io_or_file.respond_to?(:seek)
115
144
  io = io_or_file.dup
116
145
  io.seek(offset, ::IO::SEEK_SET)
@@ -122,48 +151,78 @@ module Zip
122
151
  end
123
152
  end
124
153
 
125
- def open_entry
154
+ def open_entry # :nodoc:
126
155
  @current_entry = ::Zip::Entry.read_local_entry(@archive_io)
127
- if @current_entry && @current_entry.gp_flags & 1 == 1 && @decrypter.is_a?(NullEncrypter)
128
- raise Error, 'password required to decode zip file'
129
- end
130
- if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \
131
- && @current_entry.compressed_size == 0 \
132
- && @current_entry.size == 0 && !@complete_entry
133
- raise GPFBit3Error,
134
- 'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \
135
- 'Please use ::Zip::File instead of ::Zip::InputStream'
156
+ return if @current_entry.nil?
157
+
158
+ if @current_entry.incomplete? && @current_entry.compressed_size == 0 && !@complete_entry
159
+ raise StreamingError, @current_entry
136
160
  end
137
- @decompressor = get_decompressor
161
+
162
+ @decompressor = assemble_io
138
163
  flush
139
164
  @current_entry
140
165
  end
141
166
 
142
- def get_decompressor
143
- if @current_entry.nil?
144
- ::Zip::NullDecompressor
145
- elsif @current_entry.compression_method == ::Zip::Entry::STORED
146
- if @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry
147
- ::Zip::PassThruDecompressor.new(@archive_io, @complete_entry.size)
167
+ def assemble_io # :nodoc:
168
+ io = if @current_entry.encrypted?
169
+ raise Error, 'A password is required to decode this zip file.' if @decrypter.nil?
170
+
171
+ get_decrypted_io
172
+ else
173
+ @archive_io
174
+ end
175
+
176
+ get_decompressor(io)
177
+ end
178
+
179
+ def get_decrypted_io # :nodoc:
180
+ header = @archive_io.read(@decrypter.header_bytesize)
181
+ @decrypter.reset!(header)
182
+
183
+ compressed_size =
184
+ if @current_entry.incomplete? && @current_entry.crc == 0 &&
185
+ @current_entry.compressed_size == 0 && @complete_entry
186
+ @complete_entry.compressed_size
148
187
  else
149
- ::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size)
188
+ @current_entry.compressed_size
150
189
  end
151
- elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED
152
- header = @archive_io.read(@decrypter.header_bytesize)
153
- @decrypter.reset!(header)
154
- ::Zip::Inflater.new(@archive_io, @decrypter)
155
- else
156
- raise ::Zip::CompressionMethodError,
157
- "Unsupported compression method #{@current_entry.compression_method}"
190
+
191
+ if @decrypter.kind_of?(::Zip::AESDecrypter)
192
+ compressed_size -= @decrypter.header_bytesize
193
+ compressed_size -= ::Zip::AESEncryption::AUTHENTICATION_CODE_LENGTH
158
194
  end
195
+
196
+ ::Zip::DecryptedIo.new(@archive_io, @decrypter, compressed_size)
197
+ end
198
+
199
+ def get_decompressor(io) # :nodoc:
200
+ return ::Zip::NullDecompressor if @current_entry.nil?
201
+
202
+ decompressed_size =
203
+ if @current_entry.incomplete? && @current_entry.crc == 0 &&
204
+ @current_entry.size == 0 && @complete_entry
205
+ @complete_entry.size
206
+ else
207
+ @current_entry.size
208
+ end
209
+
210
+ decompressor_class = ::Zip::Decompressor.find_by_compression_method(
211
+ @current_entry.compression_method
212
+ )
213
+ if decompressor_class.nil?
214
+ raise ::Zip::CompressionMethodError, @current_entry.compression_method
215
+ end
216
+
217
+ decompressor_class.new(io, decompressed_size)
159
218
  end
160
219
 
161
- def produce_input
162
- @decompressor.produce_input
220
+ def produce_input(maxlen = CHUNK_SIZE) # :nodoc:
221
+ @decompressor.read(maxlen)
163
222
  end
164
223
 
165
- def input_finished?
166
- @decompressor.input_finished?
224
+ def input_finished? # :nodoc:
225
+ @decompressor.eof?
167
226
  end
168
227
  end
169
228
  end
@@ -1,111 +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 = ''
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 = '')
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 = ''
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?
56
+
38
57
  return ''
39
58
  end
40
59
 
41
60
  @pos += tbuf.length
42
61
 
43
- if buf
44
- buf.replace(tbuf)
62
+ if out_string.nil?
63
+ tbuf.force_encoding(Encoding::ASCII_8BIT)
45
64
  else
46
- buf = tbuf
65
+ encoding = out_string.encoding
66
+ out_string.replace(tbuf).force_encoding(encoding)
47
67
  end
48
- buf
49
68
  end
50
69
 
51
- def readlines(a_sep_string = $/)
52
- ret_val = []
53
- each_line(a_sep_string) { |line| ret_val << line }
54
- 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
55
91
  end
56
92
 
57
- def gets(a_sep_string = $/, number_of_bytes = nil)
58
- @lineno = @lineno.next
59
-
60
- if number_of_bytes.respond_to?(:to_int)
61
- number_of_bytes = number_of_bytes.to_int
62
- a_sep_string = a_sep_string.to_str if a_sep_string
63
- elsif a_sep_string.respond_to?(:to_int)
64
- number_of_bytes = a_sep_string.to_int
65
- a_sep_string = $/
66
- else
67
- number_of_bytes = nil
68
- a_sep_string = a_sep_string.to_str if a_sep_string
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)
69
118
  end
70
119
 
71
- return read(number_of_bytes) if a_sep_string.nil?
72
- a_sep_string = "#{$/}#{$/}" if a_sep_string.empty?
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}"
125
+ end
73
126
 
74
127
  buffer_index = 0
75
- over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes)
76
- while (match_index = @output_buffer.index(a_sep_string, buffer_index)).nil? && !over_limit
77
- buffer_index = [buffer_index, @output_buffer.bytesize - a_sep_string.bytesize].max
78
- return @output_buffer.empty? ? nil : flush if input_finished?
128
+ while (sep_index = @output_buffer.index(sep, buffer_index)).nil?
129
+ break if limit && @output_buffer.bytesize >= limit
130
+
131
+ if input_finished?
132
+ return nil if @output_buffer.empty?
133
+
134
+ @lineno = @lineno.next
135
+ @pos += @output_buffer.bytesize
136
+ return @output_buffer.slice!(0..)
137
+ end
138
+
139
+ buffer_index = [buffer_index, @output_buffer.bytesize - sep.bytesize].max
79
140
  @output_buffer << produce_input
80
- over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes)
81
141
  end
82
- sep_index = [match_index + a_sep_string.bytesize, number_of_bytes || @output_buffer.bytesize].min
83
- @pos += sep_index
84
- @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)
85
148
  end
86
149
 
87
- def ungetc(byte)
150
+ def ungetc(byte) # :nodoc:
88
151
  @output_buffer = byte.chr + @output_buffer
89
152
  end
90
153
 
91
- def flush
92
- ret_val = @output_buffer
93
- @output_buffer = ''
94
- ret_val
154
+ def flush # :nodoc:
155
+ @output_buffer.slice!(0..)
95
156
  end
96
157
 
97
- def readline(a_sep_string = $/)
98
- ret_val = gets(a_sep_string)
99
- raise EOFError unless ret_val
100
- 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?
165
+
166
+ gets(sep, limit, chomp: chomp)
101
167
  end
102
168
 
103
- def each_line(a_sep_string = $/)
104
- yield readline(a_sep_string) while true
105
- rescue EOFError
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
195
+ end
196
+
197
+ alias each_line each
198
+
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?
203
+ @output_buffer.empty? && input_finished?
106
204
  end
107
205
 
108
- alias_method :each, :each_line
206
+ # Alias for compatibility. Remove for version 4.
207
+ alias eof eof? # :nodoc:
109
208
  end
110
209
  end
111
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($,) << $\.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,31 +1,31 @@
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, '')) 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, ''))
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
27
27
  end
28
- end # IOExtras namespace module
28
+ end
29
29
  end
30
30
 
31
31
  require 'zip/ioextras/abstract_input_stream'