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.
- checksums.yaml +4 -4
- data/Changelog.md +516 -0
- data/LICENSE.md +24 -0
- data/README.md +213 -51
- data/Rakefile +17 -12
- data/lib/rubyzip.rb +3 -0
- data/lib/zip/central_directory.rb +196 -123
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +81 -21
- data/lib/zip/crypto/aes_encryption.rb +120 -0
- data/lib/zip/crypto/decrypted_io.rb +46 -0
- data/lib/zip/crypto/encryption.rb +4 -2
- data/lib/zip/crypto/null_encryption.rb +5 -13
- data/lib/zip/crypto/traditional_encryption.rb +22 -16
- data/lib/zip/decompressor.rb +21 -2
- data/lib/zip/deflater.rb +12 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +65 -13
- data/lib/zip/entry.rb +433 -249
- data/lib/zip/entry_set.rb +13 -9
- data/lib/zip/errors.rb +152 -15
- data/lib/zip/extra_field/aes.rb +50 -0
- data/lib/zip/extra_field/generic.rb +18 -18
- data/lib/zip/extra_field/ntfs.rb +10 -4
- data/lib/zip/extra_field/old_unix.rb +6 -2
- data/lib/zip/extra_field/universal_time.rb +45 -13
- data/lib/zip/extra_field/unix.rb +7 -3
- data/lib/zip/extra_field/unknown.rb +35 -0
- data/lib/zip/extra_field/zip64.rb +23 -7
- data/lib/zip/extra_field.rb +31 -27
- data/lib/zip/file.rb +225 -241
- 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 +32 -585
- data/lib/zip/inflater.rb +28 -37
- data/lib/zip/input_stream.rb +113 -54
- data/lib/zip/ioextras/abstract_input_stream.rb +154 -55
- data/lib/zip/ioextras/abstract_output_stream.rb +13 -3
- data/lib/zip/ioextras.rb +8 -8
- data/lib/zip/null_compressor.rb +3 -1
- data/lib/zip/null_decompressor.rb +7 -12
- data/lib/zip/null_input_stream.rb +3 -1
- data/lib/zip/output_stream.rb +69 -50
- data/lib/zip/pass_thru_compressor.rb +5 -3
- data/lib/zip/pass_thru_decompressor.rb +17 -23
- data/lib/zip/streamable_directory.rb +6 -4
- data/lib/zip/streamable_stream.rb +10 -11
- data/lib/zip/version.rb +4 -1
- data/lib/zip.rb +32 -7
- data/rubyzip.gemspec +39 -0
- data/samples/example.rb +9 -4
- data/samples/example_filesystem.rb +4 -3
- data/samples/example_recursive.rb +3 -1
- data/samples/gtk_ruby_zip.rb +23 -21
- data/samples/qtzip.rb +13 -12
- data/samples/write_simple.rb +3 -4
- data/samples/zipfind.rb +24 -22
- metadata +85 -177
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
- data/test/basic_zip_file_test.rb +0 -60
- data/test/case_sensitivity_test.rb +0 -69
- data/test/central_directory_entry_test.rb +0 -69
- data/test/central_directory_test.rb +0 -100
- data/test/crypto/null_encryption_test.rb +0 -57
- data/test/crypto/traditional_encryption_test.rb +0 -80
- data/test/data/WarnInvalidDate.zip +0 -0
- data/test/data/file1.txt +0 -46
- data/test/data/file1.txt.deflatedData +0 -0
- data/test/data/file2.txt +0 -1504
- data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
- data/test/data/globTest/foo.txt +0 -0
- data/test/data/globTest/food.txt +0 -0
- data/test/data/globTest.zip +0 -0
- data/test/data/gpbit3stored.zip +0 -0
- data/test/data/mimetype +0 -1
- data/test/data/notzippedruby.rb +0 -7
- data/test/data/ntfs.zip +0 -0
- data/test/data/oddExtraField.zip +0 -0
- data/test/data/path_traversal/Makefile +0 -10
- data/test/data/path_traversal/jwilk/README.md +0 -5
- data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
- data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
- data/test/data/path_traversal/jwilk/relative0.zip +0 -0
- data/test/data/path_traversal/jwilk/relative2.zip +0 -0
- data/test/data/path_traversal/jwilk/symlink.zip +0 -0
- data/test/data/path_traversal/relative1.zip +0 -0
- data/test/data/path_traversal/tilde.zip +0 -0
- data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
- data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
- data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
- data/test/data/rubycode.zip +0 -0
- data/test/data/rubycode2.zip +0 -0
- data/test/data/test.xls +0 -0
- data/test/data/testDirectory.bin +0 -0
- data/test/data/zip64-sample.zip +0 -0
- data/test/data/zipWithDirs.zip +0 -0
- data/test/data/zipWithEncryption.zip +0 -0
- data/test/deflater_test.rb +0 -65
- data/test/encryption_test.rb +0 -42
- data/test/entry_set_test.rb +0 -163
- data/test/entry_test.rb +0 -154
- data/test/errors_test.rb +0 -35
- data/test/extra_field_test.rb +0 -76
- data/test/file_extract_directory_test.rb +0 -54
- data/test/file_extract_test.rb +0 -145
- data/test/file_permissions_test.rb +0 -65
- data/test/file_split_test.rb +0 -57
- data/test/file_test.rb +0 -666
- data/test/filesystem/dir_iterator_test.rb +0 -58
- data/test/filesystem/directory_test.rb +0 -139
- data/test/filesystem/file_mutating_test.rb +0 -87
- data/test/filesystem/file_nonmutating_test.rb +0 -508
- data/test/filesystem/file_stat_test.rb +0 -64
- data/test/gentestfiles.rb +0 -126
- data/test/inflater_test.rb +0 -14
- data/test/input_stream_test.rb +0 -182
- data/test/ioextras/abstract_input_stream_test.rb +0 -102
- data/test/ioextras/abstract_output_stream_test.rb +0 -106
- data/test/ioextras/fake_io_test.rb +0 -18
- data/test/local_entry_test.rb +0 -154
- data/test/output_stream_test.rb +0 -128
- data/test/pass_thru_compressor_test.rb +0 -30
- data/test/pass_thru_decompressor_test.rb +0 -14
- data/test/path_traversal_test.rb +0 -141
- data/test/samples/example_recursive_test.rb +0 -37
- data/test/settings_test.rb +0 -95
- data/test/test_helper.rb +0 -234
- data/test/unicode_file_names_and_comments_test.rb +0 -62
- data/test/zip64_full_test.rb +0 -51
- 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
|
|
3
|
-
def initialize(
|
|
4
|
-
super
|
|
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
|
-
|
|
12
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
37
|
-
alias
|
|
28
|
+
# Alias for compatibility. Remove for version 4.
|
|
29
|
+
alias eof eof?
|
|
38
30
|
|
|
39
31
|
private
|
|
40
32
|
|
|
41
|
-
def
|
|
33
|
+
def produce_input
|
|
42
34
|
retried = 0
|
|
43
35
|
begin
|
|
44
|
-
@zlib_inflater.inflate(
|
|
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
|
|
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
|
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,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
|
|
54
|
+
def initialize(context, offset: 0, decrypter: nil)
|
|
51
55
|
super()
|
|
52
56
|
@archive_io = get_io(context, offset)
|
|
53
|
-
@decompressor
|
|
54
|
-
@decrypter
|
|
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
|
|
63
|
-
# method on a newly created
|
|
64
|
-
# the first entry in the archive.
|
|
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
|
-
@
|
|
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
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
161
|
+
|
|
162
|
+
@decompressor = assemble_io
|
|
138
163
|
flush
|
|
139
164
|
@current_entry
|
|
140
165
|
end
|
|
141
166
|
|
|
142
|
-
def
|
|
143
|
-
if @current_entry.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
188
|
+
@current_entry.compressed_size
|
|
150
189
|
end
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@decrypter.
|
|
154
|
-
::Zip::
|
|
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.
|
|
220
|
+
def produce_input(maxlen = CHUNK_SIZE) # :nodoc:
|
|
221
|
+
@decompressor.read(maxlen)
|
|
163
222
|
end
|
|
164
223
|
|
|
165
|
-
def input_finished?
|
|
166
|
-
@decompressor.
|
|
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
|
-
|
|
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
|
|
23
|
-
@output_buffer.slice!(0,
|
|
40
|
+
if maxlen && maxlen <= @output_buffer.bytesize
|
|
41
|
+
@output_buffer.slice!(0, maxlen)
|
|
24
42
|
else
|
|
25
|
-
|
|
26
|
-
rbuf =
|
|
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
|
-
|
|
51
|
+
produce_input(maxlen)
|
|
34
52
|
end
|
|
35
53
|
|
|
36
54
|
if tbuf.nil? || tbuf.empty?
|
|
37
|
-
return nil if
|
|
55
|
+
return nil if maxlen&.positive?
|
|
56
|
+
|
|
38
57
|
return ''
|
|
39
58
|
end
|
|
40
59
|
|
|
41
60
|
@pos += tbuf.length
|
|
42
61
|
|
|
43
|
-
if
|
|
44
|
-
|
|
62
|
+
if out_string.nil?
|
|
63
|
+
tbuf.force_encoding(Encoding::ASCII_8BIT)
|
|
45
64
|
else
|
|
46
|
-
|
|
65
|
+
encoding = out_string.encoding
|
|
66
|
+
out_string.replace(tbuf).force_encoding(encoding)
|
|
47
67
|
end
|
|
48
|
-
buf
|
|
49
68
|
end
|
|
50
69
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
93
|
-
@output_buffer = ''
|
|
94
|
-
ret_val
|
|
154
|
+
def flush # :nodoc:
|
|
155
|
+
@output_buffer.slice!(0..)
|
|
95
156
|
end
|
|
96
157
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
28
|
+
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
require 'zip/ioextras/abstract_input_stream'
|