rubyzip 2.4.1 → 3.7.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.
- checksums.yaml +4 -4
- data/Changelog.md +549 -0
- data/LICENSE.md +24 -0
- data/README.md +250 -44
- data/Rakefile +15 -13
- data/lib/rubyzip.rb +3 -0
- data/lib/zip/central_directory.rb +194 -125
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +30 -21
- data/lib/zip/crypto/aes_encryption.rb +213 -0
- data/lib/zip/crypto/decrypted_io.rb +27 -15
- data/lib/zip/crypto/encryption.rb +13 -2
- data/lib/zip/crypto/null_encryption.rb +5 -13
- data/lib/zip/crypto/traditional_encryption.rb +13 -7
- data/lib/zip/decompressor.rb +4 -3
- data/lib/zip/deflater.rb +12 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +64 -6
- data/lib/zip/entry.rb +432 -270
- data/lib/zip/entry_set.rb +11 -9
- data/lib/zip/errors.rb +152 -16
- data/lib/zip/extra_field/aes.rb +50 -0
- data/lib/zip/extra_field/generic.rb +10 -11
- data/lib/zip/extra_field/ntfs.rb +6 -4
- data/lib/zip/extra_field/old_unix.rb +5 -3
- data/lib/zip/extra_field/universal_time.rb +3 -1
- data/lib/zip/extra_field/unix.rb +7 -5
- data/lib/zip/extra_field/unknown.rb +35 -0
- data/lib/zip/extra_field/zip64.rb +19 -5
- data/lib/zip/extra_field.rb +25 -23
- data/lib/zip/file.rb +261 -262
- data/lib/zip/file_split.rb +91 -0
- data/lib/zip/filesystem/dir.rb +86 -0
- data/lib/zip/filesystem/directory_iterator.rb +48 -0
- data/lib/zip/filesystem/file.rb +263 -0
- data/lib/zip/filesystem/file_stat.rb +110 -0
- data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
- data/lib/zip/filesystem.rb +27 -596
- data/lib/zip/inflater.rb +13 -10
- data/lib/zip/input_stream.rb +102 -61
- data/lib/zip/ioextras/abstract_input_stream.rb +156 -58
- data/lib/zip/ioextras/abstract_output_stream.rb +15 -3
- data/lib/zip/ioextras/fake_io.rb +39 -0
- data/lib/zip/ioextras.rb +12 -16
- data/lib/zip/null_compressor.rb +3 -1
- data/lib/zip/null_decompressor.rb +6 -3
- data/lib/zip/null_input_stream.rb +3 -1
- data/lib/zip/output_stream.rb +76 -64
- data/lib/zip/pass_thru_compressor.rb +6 -3
- data/lib/zip/pass_thru_decompressor.rb +12 -9
- data/lib/zip/streamable_directory.rb +3 -1
- data/lib/zip/streamable_stream.rb +4 -1
- data/lib/zip/version.rb +4 -1
- data/lib/zip.rb +28 -23
- data/rubyzip.gemspec +39 -0
- data/samples/example.rb +8 -3
- data/samples/example_filesystem.rb +3 -2
- data/samples/example_recursive.rb +3 -1
- data/samples/gtk_ruby_zip.rb +5 -3
- data/samples/qtzip.rb +7 -6
- data/samples/write_simple.rb +2 -1
- data/samples/zipfind.rb +1 -0
- metadata +88 -52
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
data/lib/zip/input_stream.rb
CHANGED
|
@@ -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,37 @@ 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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
54
|
+
# This method also accepts the standard IO encoding options:
|
|
55
|
+
# `external_encoding:`, `internal_encoding:` and `encoding:`.
|
|
56
|
+
def initialize(context, offset: 0, decrypter: nil, **opts)
|
|
57
|
+
super(**opts)
|
|
58
|
+
@archive_io = get_io(context, offset)
|
|
59
|
+
@decompressor = ::Zip::NullDecompressor
|
|
60
|
+
@decrypter = decrypter
|
|
63
61
|
@current_entry = nil
|
|
62
|
+
@complete_entry = nil
|
|
64
63
|
end
|
|
65
64
|
|
|
65
|
+
# Close this InputStream. All further IO will raise an IOError.
|
|
66
66
|
def close
|
|
67
67
|
@archive_io.close
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
# Returns
|
|
71
|
-
# method on a newly created
|
|
72
|
-
# the first entry in the archive.
|
|
73
|
-
# no more entries.
|
|
70
|
+
# Returns an Entry object and positions the stream at the beginning of
|
|
71
|
+
# the entry data. It is necessary to call this method on a newly created
|
|
72
|
+
# InputStream before reading from the first entry in the archive.
|
|
73
|
+
# Returns nil when there are no more entries.
|
|
74
74
|
def get_next_entry
|
|
75
|
-
@
|
|
75
|
+
unless @current_entry.nil?
|
|
76
|
+
raise StreamingError, @current_entry if @current_entry.incomplete?
|
|
77
|
+
|
|
78
|
+
@archive_io.seek(@current_entry.next_header_offset, IO::SEEK_SET)
|
|
79
|
+
end
|
|
80
|
+
|
|
76
81
|
open_entry
|
|
77
82
|
end
|
|
78
83
|
|
|
79
|
-
# Rewinds the stream to the beginning of the current entry
|
|
84
|
+
# Rewinds the stream to the beginning of the current entry.
|
|
80
85
|
def rewind
|
|
81
86
|
return if @current_entry.nil?
|
|
82
87
|
|
|
@@ -86,23 +91,44 @@ module Zip
|
|
|
86
91
|
open_entry
|
|
87
92
|
end
|
|
88
93
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
# Modelled after IO#sysread.
|
|
95
|
+
#
|
|
96
|
+
# Reads up to maxlen bytes from the stream; returns a string
|
|
97
|
+
# (either a new string or the given out_string).
|
|
98
|
+
# Its encoding is the unchanged encoding of out_string, if out_string is
|
|
99
|
+
# given; ASCII-8BIT, otherwise. Output contains maxlen bytes from the
|
|
100
|
+
# stream, if available; otherwise contains all available bytes, if any
|
|
101
|
+
# available; otherwise is an empty string.
|
|
102
|
+
#
|
|
103
|
+
# This method should not be used with buffered input stream-reader methods,
|
|
104
|
+
# such as #read, #readline, #gets.
|
|
105
|
+
def sysread(maxlen = nil, out_string = nil)
|
|
106
|
+
# Remove the default value for maxlen for version 4.
|
|
107
|
+
return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
|
|
108
|
+
|
|
109
|
+
output = produce_input(maxlen)
|
|
110
|
+
|
|
111
|
+
if out_string.nil?
|
|
112
|
+
output.force_encoding(@internal_encoding || @external_encoding)
|
|
113
|
+
else
|
|
114
|
+
encoding = out_string.encoding
|
|
115
|
+
out_string.replace(output).force_encoding(encoding)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns the size of the current entry, or `nil` if there isn't one.
|
|
120
|
+
def size
|
|
121
|
+
return if @current_entry.nil?
|
|
122
|
+
|
|
123
|
+
@current_entry.size
|
|
92
124
|
end
|
|
93
125
|
|
|
94
126
|
class << self
|
|
95
127
|
# Same as #initialize but if a block is passed the opened
|
|
96
128
|
# stream is passed to the block and closed when the block
|
|
97
129
|
# returns.
|
|
98
|
-
def open(filename_or_io,
|
|
99
|
-
|
|
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)
|
|
130
|
+
def open(filename_or_io, offset: 0, decrypter: nil, **opts)
|
|
131
|
+
zio = new(filename_or_io, offset: offset, decrypter: decrypter, **opts)
|
|
106
132
|
return zio unless block_given?
|
|
107
133
|
|
|
108
134
|
begin
|
|
@@ -111,17 +137,11 @@ module Zip
|
|
|
111
137
|
zio.close if zio
|
|
112
138
|
end
|
|
113
139
|
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
140
|
end
|
|
121
141
|
|
|
122
142
|
protected
|
|
123
143
|
|
|
124
|
-
def get_io(io_or_file, offset = 0)
|
|
144
|
+
def get_io(io_or_file, offset = 0) # :nodoc:
|
|
125
145
|
if io_or_file.respond_to?(:seek)
|
|
126
146
|
io = io_or_file.dup
|
|
127
147
|
io.seek(offset, ::IO::SEEK_SET)
|
|
@@ -133,57 +153,78 @@ module Zip
|
|
|
133
153
|
end
|
|
134
154
|
end
|
|
135
155
|
|
|
136
|
-
def open_entry
|
|
156
|
+
def open_entry # :nodoc:
|
|
137
157
|
@current_entry = ::Zip::Entry.read_local_entry(@archive_io)
|
|
138
|
-
if @current_entry
|
|
139
|
-
raise Error, 'password required to decode zip file'
|
|
140
|
-
end
|
|
158
|
+
return if @current_entry.nil?
|
|
141
159
|
|
|
142
|
-
if @current_entry
|
|
143
|
-
|
|
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'
|
|
160
|
+
if @current_entry.incomplete? && @current_entry.compressed_size == 0 && !@complete_entry
|
|
161
|
+
raise StreamingError, @current_entry
|
|
148
162
|
end
|
|
149
|
-
|
|
150
|
-
@decompressor =
|
|
163
|
+
|
|
164
|
+
@decompressor = assemble_io
|
|
151
165
|
flush
|
|
152
166
|
@current_entry
|
|
153
167
|
end
|
|
154
168
|
|
|
155
|
-
def
|
|
169
|
+
def assemble_io # :nodoc:
|
|
170
|
+
io = if @current_entry.encrypted?
|
|
171
|
+
raise Error, 'A password is required to decode this zip file.' if @decrypter.nil?
|
|
172
|
+
|
|
173
|
+
get_decrypted_io
|
|
174
|
+
else
|
|
175
|
+
@archive_io
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
get_decompressor(io)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def get_decrypted_io # :nodoc:
|
|
156
182
|
header = @archive_io.read(@decrypter.header_bytesize)
|
|
157
183
|
@decrypter.reset!(header)
|
|
158
184
|
|
|
159
|
-
|
|
185
|
+
compressed_size =
|
|
186
|
+
if @current_entry.incomplete? && @current_entry.crc == 0 &&
|
|
187
|
+
@current_entry.compressed_size == 0 && @complete_entry
|
|
188
|
+
@complete_entry.compressed_size
|
|
189
|
+
else
|
|
190
|
+
@current_entry.compressed_size
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
if @decrypter.kind_of?(::Zip::AESDecrypter)
|
|
194
|
+
compressed_size -= @decrypter.header_bytesize
|
|
195
|
+
compressed_size -= ::Zip::AESEncryption::AUTHENTICATION_CODE_LENGTH
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
::Zip::DecryptedIo.new(@archive_io, @decrypter, compressed_size)
|
|
160
199
|
end
|
|
161
200
|
|
|
162
|
-
def get_decompressor
|
|
201
|
+
def get_decompressor(io) # :nodoc:
|
|
163
202
|
return ::Zip::NullDecompressor if @current_entry.nil?
|
|
164
203
|
|
|
165
204
|
decompressed_size =
|
|
166
|
-
if @current_entry.incomplete? && @current_entry.crc == 0 &&
|
|
205
|
+
if @current_entry.incomplete? && @current_entry.crc == 0 &&
|
|
206
|
+
@current_entry.size == 0 && @complete_entry
|
|
167
207
|
@complete_entry.size
|
|
168
208
|
else
|
|
169
209
|
@current_entry.size
|
|
170
210
|
end
|
|
171
211
|
|
|
172
|
-
decompressor_class = ::Zip::Decompressor.find_by_compression_method(
|
|
212
|
+
decompressor_class = ::Zip::Decompressor.find_by_compression_method(
|
|
213
|
+
@current_entry.compression_method
|
|
214
|
+
)
|
|
173
215
|
if decompressor_class.nil?
|
|
174
|
-
raise ::Zip::CompressionMethodError,
|
|
175
|
-
"Unsupported compression method #{@current_entry.compression_method}"
|
|
216
|
+
raise ::Zip::CompressionMethodError, @current_entry.compression_method
|
|
176
217
|
end
|
|
177
218
|
|
|
178
|
-
decompressor_class.new(
|
|
219
|
+
decompressor_class.new(io, decompressed_size)
|
|
179
220
|
end
|
|
180
221
|
|
|
181
|
-
def produce_input
|
|
182
|
-
@decompressor.read(
|
|
222
|
+
def produce_input(maxlen = CHUNK_SIZE) # :nodoc:
|
|
223
|
+
@decompressor.read(maxlen)
|
|
183
224
|
end
|
|
184
225
|
|
|
185
|
-
def input_finished?
|
|
186
|
-
@decompressor.eof
|
|
226
|
+
def input_finished? # :nodoc:
|
|
227
|
+
@decompressor.eof?
|
|
187
228
|
end
|
|
188
229
|
end
|
|
189
230
|
end
|
|
@@ -1,122 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'fake_io'
|
|
4
|
+
|
|
1
5
|
module Zip
|
|
2
|
-
module IOExtras
|
|
6
|
+
module IOExtras # :nodoc:
|
|
3
7
|
# Implements many of the convenience methods of IO
|
|
4
|
-
# such as gets, getc, readline and readlines
|
|
8
|
+
# such as gets, getc, read, readline and readlines
|
|
5
9
|
# depends on: input_finished?, produce_input and read
|
|
6
10
|
module AbstractInputStream
|
|
7
11
|
include Enumerable
|
|
8
12
|
include FakeIO
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
# Creates a new input stream wrapper.
|
|
15
|
+
#
|
|
16
|
+
# This method accepts the standard IO encoding options:
|
|
17
|
+
# `external_encoding:`, `internal_encoding:` and `encoding:`.
|
|
18
|
+
def initialize(**opts)
|
|
11
19
|
super
|
|
12
20
|
@lineno = 0
|
|
13
21
|
@pos = 0
|
|
14
|
-
@output_buffer = ''.b
|
|
22
|
+
@output_buffer = +''.b
|
|
15
23
|
end
|
|
16
24
|
|
|
25
|
+
# Returns (or sets) the current line number in the decompressed
|
|
26
|
+
# (possibly decrypted) data stream. See the Line Number documentation
|
|
27
|
+
# for the IO class for more information.
|
|
17
28
|
attr_accessor :lineno
|
|
29
|
+
|
|
30
|
+
# Returns the current position (in bytes) in the decompressed (possibly
|
|
31
|
+
# decrypted) data stream.
|
|
18
32
|
attr_reader :pos
|
|
19
33
|
|
|
20
|
-
|
|
34
|
+
# Reads bytes from the stream decompressed (possibly decrypted) data
|
|
35
|
+
# stream. If `maxlen` is `nil`, reads all bytes; otherwise, reads up to
|
|
36
|
+
# `maxlen` bytes. If `maxlen` is zero, returns an empty string.
|
|
37
|
+
#
|
|
38
|
+
# Returns a string (either a new string or the given `out_string`)
|
|
39
|
+
# containing the bytes read. The string's encoding is the unchanged
|
|
40
|
+
# encoding of `out_string`, if `out_string` is given; `ASCII-8BIT`,
|
|
41
|
+
# otherwise.
|
|
42
|
+
def read(maxlen = nil, out_string = nil) # rubocop:disable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity
|
|
43
|
+
return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
|
|
44
|
+
|
|
21
45
|
tbuf = if @output_buffer.bytesize > 0
|
|
22
|
-
if
|
|
23
|
-
@output_buffer.slice!(0,
|
|
46
|
+
if maxlen && maxlen <= @output_buffer.bytesize
|
|
47
|
+
@output_buffer.slice!(0, maxlen)
|
|
24
48
|
else
|
|
25
|
-
|
|
26
|
-
rbuf =
|
|
49
|
+
maxlen -= @output_buffer.bytesize if maxlen
|
|
50
|
+
rbuf = produce_input(maxlen)
|
|
27
51
|
out = @output_buffer
|
|
28
52
|
out << rbuf if rbuf
|
|
29
|
-
@output_buffer = ''.b
|
|
53
|
+
@output_buffer = +''.b
|
|
30
54
|
out
|
|
31
55
|
end
|
|
32
56
|
else
|
|
33
|
-
|
|
57
|
+
produce_input(maxlen)
|
|
34
58
|
end
|
|
35
59
|
|
|
36
60
|
if tbuf.nil? || tbuf.empty?
|
|
37
|
-
return nil if
|
|
61
|
+
return nil if maxlen&.positive?
|
|
38
62
|
|
|
39
63
|
return ''
|
|
40
64
|
end
|
|
41
65
|
|
|
42
66
|
@pos += tbuf.length
|
|
43
67
|
|
|
44
|
-
if
|
|
45
|
-
|
|
68
|
+
if out_string.nil?
|
|
69
|
+
tbuf.force_encoding(@internal_encoding || @external_encoding)
|
|
46
70
|
else
|
|
47
|
-
|
|
71
|
+
encoding = out_string.encoding
|
|
72
|
+
out_string.replace(tbuf).force_encoding(encoding)
|
|
48
73
|
end
|
|
49
|
-
buf
|
|
50
74
|
end
|
|
51
75
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
76
|
+
# Reads and returns all remaining lines from the stream. See the Line IO
|
|
77
|
+
# documentation in the IO class for more information.
|
|
78
|
+
#
|
|
79
|
+
# With no arguments given, returns lines as determined by line
|
|
80
|
+
# separator `$/`, or `nil` if none.
|
|
81
|
+
#
|
|
82
|
+
# With only string argument `sep` given, returns lines as
|
|
83
|
+
# determined by line separator `sep`, or `nil` if none. See the
|
|
84
|
+
# Line Separator documentation in the IO class for more information.
|
|
85
|
+
# The two special values for `sep` (`nil` and `""`) are honoured.
|
|
86
|
+
#
|
|
87
|
+
# With only integer argument `limit` given, limits the number of bytes
|
|
88
|
+
# in each line; see the Line Limit documentation in the IO class for more
|
|
89
|
+
# information.
|
|
90
|
+
#
|
|
91
|
+
# With arguments `sep` and `limit` given, combines the two behaviors.
|
|
92
|
+
#
|
|
93
|
+
# Optional keyword argument `chomp` specifies whether line separators
|
|
94
|
+
# are to be omitted.
|
|
95
|
+
def readlines(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
|
|
96
|
+
each(sep, limit, chomp: chomp).to_a
|
|
56
97
|
end
|
|
57
98
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
99
|
+
# Reads and returns a line from the stream. See the Line IO
|
|
100
|
+
# documentation in the IO class for more information.
|
|
101
|
+
#
|
|
102
|
+
# With no arguments given, returns the next line as determined by line
|
|
103
|
+
# separator `$/`, or `nil` if none.
|
|
104
|
+
#
|
|
105
|
+
# With only string argument `sep` given, returns the next line as
|
|
106
|
+
# determined by line separator `sep`, or `nil` if none. See the
|
|
107
|
+
# Line Separator documentation in the IO class for more information.
|
|
108
|
+
# The two special values for `sep` (`nil` and `""`) are honoured.
|
|
109
|
+
#
|
|
110
|
+
# With only integer argument `limit` given, limits the number of bytes
|
|
111
|
+
# in the line; see the Line Limit documentation in the IO class for more
|
|
112
|
+
# information.
|
|
113
|
+
#
|
|
114
|
+
# With arguments `sep` and `limit` given, combines the two behaviors.
|
|
115
|
+
#
|
|
116
|
+
# Optional keyword argument `chomp` specifies whether line separators
|
|
117
|
+
# are to be omitted.
|
|
118
|
+
def gets(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
119
|
+
if sep.nil?
|
|
120
|
+
return nil if eof?
|
|
121
|
+
|
|
122
|
+
@lineno = @lineno.next
|
|
123
|
+
return read(limit)
|
|
70
124
|
end
|
|
71
125
|
|
|
72
|
-
|
|
126
|
+
if sep.respond_to?(:to_int)
|
|
127
|
+
limit = sep.to_int
|
|
128
|
+
sep = $INPUT_RECORD_SEPARATOR
|
|
129
|
+
elsif sep&.empty?
|
|
130
|
+
sep = "#{$INPUT_RECORD_SEPARATOR}#{$INPUT_RECORD_SEPARATOR}"
|
|
131
|
+
end
|
|
73
132
|
|
|
74
|
-
|
|
133
|
+
encoding = @internal_encoding || @external_encoding
|
|
75
134
|
|
|
76
135
|
buffer_index = 0
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
136
|
+
while (sep_index = @output_buffer.index(sep, buffer_index)).nil?
|
|
137
|
+
break if limit && @output_buffer.bytesize >= limit
|
|
138
|
+
|
|
139
|
+
if input_finished?
|
|
140
|
+
return nil if @output_buffer.empty?
|
|
141
|
+
|
|
142
|
+
@lineno = @lineno.next
|
|
143
|
+
@pos += @output_buffer.bytesize
|
|
144
|
+
return @output_buffer.slice!(0..).force_encoding(encoding)
|
|
145
|
+
end
|
|
81
146
|
|
|
147
|
+
buffer_index = [buffer_index, @output_buffer.bytesize - sep.bytesize].max
|
|
82
148
|
@output_buffer << produce_input
|
|
83
|
-
over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes)
|
|
84
149
|
end
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
150
|
+
|
|
151
|
+
limit ||= @output_buffer.bytesize
|
|
152
|
+
cut_index = sep_index ? [sep_index + sep.bytesize, limit].min : limit
|
|
153
|
+
@lineno = @lineno.next
|
|
154
|
+
@pos += cut_index
|
|
155
|
+
data = @output_buffer.slice!(0, cut_index)
|
|
156
|
+
data&.chomp!(sep) if chomp
|
|
157
|
+
data&.force_encoding(encoding)
|
|
88
158
|
end
|
|
89
159
|
|
|
90
|
-
def ungetc(byte)
|
|
160
|
+
def ungetc(byte) # :nodoc:
|
|
91
161
|
@output_buffer = byte.chr + @output_buffer
|
|
92
162
|
end
|
|
93
163
|
|
|
94
|
-
def flush
|
|
95
|
-
|
|
96
|
-
@output_buffer = ''.b
|
|
97
|
-
ret_val
|
|
164
|
+
def flush # :nodoc:
|
|
165
|
+
@output_buffer.slice!(0..)
|
|
98
166
|
end
|
|
99
167
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
168
|
+
# Reads a line as with #gets, but raises `EOFError` if already at
|
|
169
|
+
# end-of-stream.
|
|
170
|
+
#
|
|
171
|
+
# Optional keyword argument `chomp` specifies whether line separators
|
|
172
|
+
# are to be omitted.
|
|
173
|
+
def readline(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
|
|
174
|
+
raise EOFError if eof?
|
|
103
175
|
|
|
104
|
-
|
|
176
|
+
gets(sep, limit, chomp: chomp)
|
|
105
177
|
end
|
|
106
178
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
179
|
+
# Calls the block with each remaining line read from the stream.
|
|
180
|
+
# Does nothing if already at end-of-stream. See the Line IO
|
|
181
|
+
# documentation in the IO class for more information.
|
|
182
|
+
#
|
|
183
|
+
# With no arguments given, reads lines as determined by line separator
|
|
184
|
+
# `$/`. With only string argument `sep` given, reads lines as determined
|
|
185
|
+
# by line separator `sep`. See the Line Separator documentation in the
|
|
186
|
+
# IO class for more information. The two special values for `sep`
|
|
187
|
+
# (`nil` and `""`) are honoured.
|
|
188
|
+
#
|
|
189
|
+
# With only integer argument `limit` given, limits the number of bytes
|
|
190
|
+
# in each line; see the Line Limit documentation in the IO class for
|
|
191
|
+
# more information.
|
|
192
|
+
#
|
|
193
|
+
# With arguments `sep` and `limit` given, combines the two behaviors.
|
|
194
|
+
#
|
|
195
|
+
# Optional keyword argument `chomp` specifies whether line separators
|
|
196
|
+
# are to be omitted.
|
|
197
|
+
#
|
|
198
|
+
# Returns an `Enumerator` if no block is given.
|
|
199
|
+
def each(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
|
|
200
|
+
return to_enum(:each, sep, limit, chomp: chomp) unless block_given?
|
|
201
|
+
|
|
202
|
+
while (line = gets(sep, limit, chomp: chomp))
|
|
203
|
+
yield line
|
|
204
|
+
end
|
|
111
205
|
end
|
|
112
206
|
|
|
113
|
-
alias each
|
|
207
|
+
alias each_line each
|
|
114
208
|
|
|
115
|
-
|
|
209
|
+
# Returns `true` if the stream is positioned at its end, `false`
|
|
210
|
+
# otherwise. See Position documentation in the IO class for more
|
|
211
|
+
# information.
|
|
212
|
+
def eof?
|
|
116
213
|
@output_buffer.empty? && input_finished?
|
|
117
214
|
end
|
|
118
215
|
|
|
119
|
-
|
|
216
|
+
# Alias for compatibility. Remove for version 4.
|
|
217
|
+
alias eof eof? # :nodoc:
|
|
120
218
|
end
|
|
121
219
|
end
|
|
122
220
|
end
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'fake_io'
|
|
4
|
+
|
|
1
5
|
module Zip
|
|
2
|
-
module IOExtras
|
|
6
|
+
module IOExtras # :nodoc:
|
|
3
7
|
# Implements many of the output convenience methods of IO.
|
|
4
8
|
# relies on <<
|
|
5
|
-
module AbstractOutputStream
|
|
9
|
+
module AbstractOutputStream # :nodoc:
|
|
6
10
|
include FakeIO
|
|
7
11
|
|
|
8
12
|
def write(data)
|
|
@@ -11,11 +15,19 @@ module Zip
|
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
def print(*params)
|
|
14
|
-
self << params.join
|
|
18
|
+
self << params.join
|
|
19
|
+
|
|
20
|
+
# Deflate doesn't like `nil`s or empty strings!
|
|
21
|
+
unless $OUTPUT_RECORD_SEPARATOR.nil? || $OUTPUT_RECORD_SEPARATOR.empty?
|
|
22
|
+
self << $OUTPUT_RECORD_SEPARATOR
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
nil
|
|
15
26
|
end
|
|
16
27
|
|
|
17
28
|
def printf(a_format_string, *params)
|
|
18
29
|
self << format(a_format_string, *params)
|
|
30
|
+
nil
|
|
19
31
|
end
|
|
20
32
|
|
|
21
33
|
def putc(an_object)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zip
|
|
4
|
+
module IOExtras # :nodoc:
|
|
5
|
+
module FakeIO # :nodoc:
|
|
6
|
+
attr_reader :external_encoding, :internal_encoding
|
|
7
|
+
|
|
8
|
+
def initialize(**opts)
|
|
9
|
+
@internal_encoding = Encoding.find(opts[:internal_encoding]) if opts[:internal_encoding]
|
|
10
|
+
|
|
11
|
+
if opts[:encoding].kind_of?(String)
|
|
12
|
+
_ext, int = opts[:encoding].split(':', 2)
|
|
13
|
+
@internal_encoding = Encoding.find(int) unless @internal_encoding || int.nil?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@external_encoding = Encoding::ASCII_8BIT
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def set_encoding(ext_enc, int_enc = nil)
|
|
20
|
+
if ext_enc.kind_of?(String) && ext_enc.include?(':')
|
|
21
|
+
_ext_enc, int_enc = ext_enc.split(':', 2)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
begin
|
|
25
|
+
@internal_encoding = Encoding.find(int_enc) if int_enc
|
|
26
|
+
rescue ArgumentError
|
|
27
|
+
warn "warning: Unsupported encoding #{int_enc} ignored"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@external_encoding = Encoding::ASCII_8BIT
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Implement kind_of? in order to pretend to be an IO object.
|
|
34
|
+
def kind_of?(object)
|
|
35
|
+
object == IO || super
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/zip/ioextras.rb
CHANGED
|
@@ -1,35 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Zip
|
|
2
|
-
module IOExtras
|
|
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, ''
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
15
|
+
tr = [toread, CHUNK_SIZE].min
|
|
16
|
+
chunk = istream.read(tr, +'')
|
|
17
|
+
break if !chunk || chunk.empty?
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
object == IO || super
|
|
19
|
+
ostream.write(chunk)
|
|
20
|
+
toread -= chunk.bytesize
|
|
21
|
+
end
|
|
26
22
|
end
|
|
27
23
|
end
|
|
28
24
|
end
|
|
29
25
|
end
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
require_relative 'ioextras/abstract_input_stream'
|
|
28
|
+
require_relative 'ioextras/abstract_output_stream'
|
|
33
29
|
|
|
34
30
|
# Copyright (C) 2002-2004 Thomas Sondergaard
|
|
35
31
|
# rubyzip is free software; you can redistribute it and/or
|