ziperu 0.1.1-aarch64-linux
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 +7 -0
- data/CHANGELOG.md +24 -0
- data/LICENSE-rubyzip.txt +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +276 -0
- data/lib/rubyzip.rb +4 -0
- data/lib/zip/central_directory.rb +5 -0
- data/lib/zip/compressor.rb +5 -0
- data/lib/zip/constants.rb +5 -0
- data/lib/zip/crypto/aes_encryption.rb +5 -0
- data/lib/zip/crypto/decrypted_io.rb +5 -0
- data/lib/zip/crypto/encryption.rb +5 -0
- data/lib/zip/crypto/null_encryption.rb +5 -0
- data/lib/zip/crypto/traditional_encryption.rb +5 -0
- data/lib/zip/crypto.rb +5 -0
- data/lib/zip/decompressor.rb +5 -0
- data/lib/zip/deflater.rb +5 -0
- data/lib/zip/dirtyable.rb +5 -0
- data/lib/zip/dos_time.rb +5 -0
- data/lib/zip/entry.rb +5 -0
- data/lib/zip/entry_set.rb +5 -0
- data/lib/zip/errors.rb +5 -0
- data/lib/zip/extra_field/aes.rb +5 -0
- data/lib/zip/extra_field/generic.rb +5 -0
- data/lib/zip/extra_field/ntfs.rb +5 -0
- data/lib/zip/extra_field/old_unix.rb +5 -0
- data/lib/zip/extra_field/universal_time.rb +5 -0
- data/lib/zip/extra_field/unix.rb +5 -0
- data/lib/zip/extra_field/unknown.rb +5 -0
- data/lib/zip/extra_field/zip64.rb +5 -0
- data/lib/zip/extra_field.rb +5 -0
- data/lib/zip/file.rb +5 -0
- data/lib/zip/file_split.rb +5 -0
- data/lib/zip/filesystem/dir.rb +5 -0
- data/lib/zip/filesystem/directory_iterator.rb +5 -0
- data/lib/zip/filesystem/file.rb +5 -0
- data/lib/zip/filesystem/file_stat.rb +5 -0
- data/lib/zip/filesystem/zip_file_name_mapper.rb +5 -0
- data/lib/zip/filesystem.rb +5 -0
- data/lib/zip/inflater.rb +5 -0
- data/lib/zip/input_stream.rb +5 -0
- data/lib/zip/ioextras/abstract_input_stream.rb +5 -0
- data/lib/zip/ioextras/abstract_output_stream.rb +5 -0
- data/lib/zip/ioextras/fake_io.rb +5 -0
- data/lib/zip/ioextras.rb +5 -0
- data/lib/zip/null_compressor.rb +5 -0
- data/lib/zip/null_decompressor.rb +5 -0
- data/lib/zip/null_input_stream.rb +5 -0
- data/lib/zip/output_stream.rb +5 -0
- data/lib/zip/pass_thru_compressor.rb +5 -0
- data/lib/zip/pass_thru_decompressor.rb +5 -0
- data/lib/zip/streamable_directory.rb +5 -0
- data/lib/zip/streamable_stream.rb +5 -0
- data/lib/zip/version.rb +5 -0
- data/lib/zip.rb +19 -0
- data/lib/ziperu/3.1/ziperu.so +0 -0
- data/lib/ziperu/3.2/ziperu.so +0 -0
- data/lib/ziperu/3.3/ziperu.so +0 -0
- data/lib/ziperu/3.4/ziperu.so +0 -0
- data/lib/ziperu/4.0/ziperu.so +0 -0
- data/lib/ziperu/central_directory.rb +136 -0
- data/lib/ziperu/compressor.rb +14 -0
- data/lib/ziperu/constants.rb +148 -0
- data/lib/ziperu/crypto.rb +72 -0
- data/lib/ziperu/decompressor.rb +35 -0
- data/lib/ziperu/deflater.rb +41 -0
- data/lib/ziperu/dirtyable.rb +48 -0
- data/lib/ziperu/dos_time.rb +87 -0
- data/lib/ziperu/entry.rb +760 -0
- data/lib/ziperu/entry_set.rb +147 -0
- data/lib/ziperu/errors.rb +154 -0
- data/lib/ziperu/extra_field/aes.rb +52 -0
- data/lib/ziperu/extra_field/generic.rb +45 -0
- data/lib/ziperu/extra_field/ntfs.rb +98 -0
- data/lib/ziperu/extra_field/old_unix.rb +52 -0
- data/lib/ziperu/extra_field/universal_time.rb +81 -0
- data/lib/ziperu/extra_field/unix.rb +45 -0
- data/lib/ziperu/extra_field/unknown.rb +37 -0
- data/lib/ziperu/extra_field/zip64.rb +86 -0
- data/lib/ziperu/extra_field.rb +109 -0
- data/lib/ziperu/file.rb +614 -0
- data/lib/ziperu/file_split.rb +93 -0
- data/lib/ziperu/filesystem/dir.rb +88 -0
- data/lib/ziperu/filesystem/directory_iterator.rb +50 -0
- data/lib/ziperu/filesystem/file.rb +265 -0
- data/lib/ziperu/filesystem/file_stat.rb +112 -0
- data/lib/ziperu/filesystem/zip_file_name_mapper.rb +83 -0
- data/lib/ziperu/filesystem.rb +76 -0
- data/lib/ziperu/inflater.rb +60 -0
- data/lib/ziperu/input_stream.rb +296 -0
- data/lib/ziperu/ioextras/abstract_input_stream.rb +137 -0
- data/lib/ziperu/ioextras/abstract_output_stream.rb +57 -0
- data/lib/ziperu/ioextras/fake_io.rb +41 -0
- data/lib/ziperu/ioextras.rb +42 -0
- data/lib/ziperu/null_compressor.rb +20 -0
- data/lib/ziperu/null_decompressor.rb +25 -0
- data/lib/ziperu/null_input_stream.rb +55 -0
- data/lib/ziperu/output_stream.rb +223 -0
- data/lib/ziperu/pass_thru_compressor.rb +29 -0
- data/lib/ziperu/pass_thru_decompressor.rb +37 -0
- data/lib/ziperu/streamable_directory.rb +15 -0
- data/lib/ziperu/streamable_stream.rb +59 -0
- data/lib/ziperu/version.rb +7 -0
- data/lib/ziperu.rb +116 -0
- metadata +156 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported verbatim from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
# Not on any ziperu code path: the Rust core compresses and decompresses.
|
|
5
|
+
# Kept so that code which references these classes keeps loading.
|
|
6
|
+
module Ziperu
|
|
7
|
+
class Inflater < Decompressor # :nodoc:all
|
|
8
|
+
def initialize(*args)
|
|
9
|
+
super
|
|
10
|
+
|
|
11
|
+
@buffer = +''.b
|
|
12
|
+
@zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def read(maxlen = nil)
|
|
16
|
+
return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
|
|
17
|
+
|
|
18
|
+
while maxlen.nil? || (@buffer.bytesize < maxlen)
|
|
19
|
+
break if input_finished?
|
|
20
|
+
|
|
21
|
+
@buffer << produce_input
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@buffer.slice!(0...(maxlen || @buffer.bytesize))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def eof?
|
|
28
|
+
@buffer.empty? && input_finished?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Alias for compatibility. Remove for version 4.
|
|
32
|
+
alias eof eof?
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def produce_input
|
|
37
|
+
retried = 0
|
|
38
|
+
begin
|
|
39
|
+
@zlib_inflater.inflate(input_stream.read(Decompressor::CHUNK_SIZE))
|
|
40
|
+
rescue Zlib::BufError
|
|
41
|
+
raise if retried >= 5 # how many times should we retry?
|
|
42
|
+
|
|
43
|
+
retried += 1
|
|
44
|
+
retry
|
|
45
|
+
end
|
|
46
|
+
rescue Zlib::Error => e
|
|
47
|
+
raise ::Ziperu::DecompressionError, e
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def input_finished?
|
|
51
|
+
@zlib_inflater.finished?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
::Ziperu::Decompressor.register(::Ziperu::COMPRESSION_METHOD_DEFLATE, ::Ziperu::Inflater)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
|
59
|
+
# rubyzip is free software; you can redistribute it and/or
|
|
60
|
+
# modify it under the terms of the ruby license.
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ziperu
|
|
4
|
+
# Reads the entries of an archive one after another, exposing each as an
|
|
5
|
+
# IO-like stream, exactly as rubyzip's `Ziperu::InputStream` does:
|
|
6
|
+
#
|
|
7
|
+
# Ziperu::InputStream.open("my.zip") do |io|
|
|
8
|
+
# while (entry = io.get_next_entry)
|
|
9
|
+
# puts "Contents of #{entry.name}: '#{io.read}'"
|
|
10
|
+
# end
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# Unlike rubyzip, entries are located through the central directory rather
|
|
14
|
+
# than by walking local headers, so archives whose local headers lack sizes
|
|
15
|
+
# (general-purpose bit 3, as written by OS X Archive Utility and most
|
|
16
|
+
# streaming writers) read fine instead of raising `Ziperu::StreamingError`.
|
|
17
|
+
class InputStream
|
|
18
|
+
CHUNK_SIZE = IOExtras::CHUNK_SIZE # :nodoc:
|
|
19
|
+
|
|
20
|
+
include ::Ziperu::IOExtras::AbstractInputStream
|
|
21
|
+
|
|
22
|
+
# Opens the indicated zip file.
|
|
23
|
+
#
|
|
24
|
+
# @param context [String||IO||StringIO] file path or IO/StringIO object
|
|
25
|
+
# @param offset [Integer] byte offset of the local header to start at
|
|
26
|
+
# This method also accepts the standard IO encoding options:
|
|
27
|
+
# `external_encoding:`, `internal_encoding:` and `encoding:`.
|
|
28
|
+
def initialize(context, offset: 0, decrypter: nil, enforce_size: ::Ziperu.validate_entry_sizes, **opts)
|
|
29
|
+
super(**opts)
|
|
30
|
+
@split = !context.kind_of?(Native::Archive) && self.class.split_archive?(context)
|
|
31
|
+
# An archive handed in (by Entry#get_input_stream) stays open for its
|
|
32
|
+
# owner; one opened here is closed with the stream. A split segment
|
|
33
|
+
# has no central directory to open; rubyzip fails on the first entry,
|
|
34
|
+
# so defer the error to `get_next_entry`.
|
|
35
|
+
@owns_archive = !context.kind_of?(Native::Archive)
|
|
36
|
+
@archive =
|
|
37
|
+
if !@owns_archive
|
|
38
|
+
context
|
|
39
|
+
elsif !@split
|
|
40
|
+
begin
|
|
41
|
+
self.class.open_archive(context)
|
|
42
|
+
rescue LimitError
|
|
43
|
+
raise
|
|
44
|
+
rescue Error => e
|
|
45
|
+
# Not an archive: rubyzip finds no entries rather than raising,
|
|
46
|
+
# and `while (entry = zis.get_next_entry)` over untrusted input
|
|
47
|
+
# relies on that.
|
|
48
|
+
@not_an_archive = e
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
@decrypter = decrypter
|
|
53
|
+
@enforce_size = enforce_size
|
|
54
|
+
# An archive with several records under one name is walked from its
|
|
55
|
+
# listing, since the native core addresses only the last of each name
|
|
56
|
+
# by index; every record is yielded, as rubyzip's walk of the local
|
|
57
|
+
# headers would.
|
|
58
|
+
@records = @archive.entries if @archive&.duplicate_names?
|
|
59
|
+
@next_index = offset.zero? || @archive.nil? ? 0 : index_at_offset(offset)
|
|
60
|
+
@reader = nil
|
|
61
|
+
@current_entry = nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Close this InputStream. All further IO will raise an IOError.
|
|
65
|
+
def close
|
|
66
|
+
@reader&.close
|
|
67
|
+
@reader = nil
|
|
68
|
+
@archive&.close if @owns_archive
|
|
69
|
+
@archive = nil
|
|
70
|
+
@not_an_archive = nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns an Entry object and positions the stream at the beginning of
|
|
74
|
+
# the entry data. Returns nil when there are no more entries.
|
|
75
|
+
def get_next_entry
|
|
76
|
+
raise SplitArchiveError if @split
|
|
77
|
+
raise IOError, 'closed stream' if closed?
|
|
78
|
+
return @current_entry = nil if @archive.nil? || @next_index.nil?
|
|
79
|
+
if @next_index >= (@records ? @records.size : @archive.entry_count)
|
|
80
|
+
# Past the last entry there is nothing to read, not the previous
|
|
81
|
+
# entry again.
|
|
82
|
+
@reader&.close
|
|
83
|
+
@reader = nil
|
|
84
|
+
flush
|
|
85
|
+
return @current_entry = nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
meta = @records ? @records[@next_index] : @archive.entry_meta(@next_index)
|
|
89
|
+
@current_entry = Entry.from_meta(@archive, meta)
|
|
90
|
+
@next_index += 1
|
|
91
|
+
open_entry
|
|
92
|
+
@current_entry
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Position the stream at an entry that already exists as an object
|
|
96
|
+
# (used by Entry#get_input_stream to avoid rebuilding it).
|
|
97
|
+
#
|
|
98
|
+
# @api private
|
|
99
|
+
def get_next_stream_entry(entry)
|
|
100
|
+
raise IOError, 'closed stream' if closed?
|
|
101
|
+
|
|
102
|
+
@current_entry = entry
|
|
103
|
+
@next_index =
|
|
104
|
+
if @records
|
|
105
|
+
at = @records.index { |meta| meta[:header_offset] == entry.local_header_offset }
|
|
106
|
+
at && at + 1
|
|
107
|
+
else
|
|
108
|
+
entry.index + 1
|
|
109
|
+
end
|
|
110
|
+
open_entry
|
|
111
|
+
@current_entry
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Rewinds the stream to the beginning of the current entry.
|
|
115
|
+
def rewind
|
|
116
|
+
raise IOError, 'closed stream' if closed?
|
|
117
|
+
return if @current_entry.nil?
|
|
118
|
+
|
|
119
|
+
@lineno = 0
|
|
120
|
+
@pos = 0
|
|
121
|
+
wrap_errors { @reader.rewind }
|
|
122
|
+
flush
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Modelled after IO#sysread. Reads up to maxlen bytes from the stream,
|
|
126
|
+
# bypassing the line buffer; returns a string (either a new string or the
|
|
127
|
+
# given out_string).
|
|
128
|
+
def sysread(maxlen = nil, out_string = nil)
|
|
129
|
+
return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
|
|
130
|
+
|
|
131
|
+
output = produce_input(maxlen) || +''.b
|
|
132
|
+
|
|
133
|
+
if out_string.nil?
|
|
134
|
+
output.force_encoding(@internal_encoding || @external_encoding)
|
|
135
|
+
else
|
|
136
|
+
encoding = out_string.encoding
|
|
137
|
+
out_string.replace(output).force_encoding(encoding)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Returns the size of the current entry, or `nil` if there isn't one.
|
|
142
|
+
def size
|
|
143
|
+
return if @current_entry.nil?
|
|
144
|
+
|
|
145
|
+
@current_entry.size
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
class << self
|
|
149
|
+
# Same as #initialize but if a block is passed the opened stream is
|
|
150
|
+
# passed to the block and closed when the block returns.
|
|
151
|
+
def open(filename_or_io, offset: 0, decrypter: nil, **opts)
|
|
152
|
+
zio = new(filename_or_io, offset: offset, decrypter: decrypter, **opts)
|
|
153
|
+
return zio unless block_given?
|
|
154
|
+
|
|
155
|
+
begin
|
|
156
|
+
yield zio
|
|
157
|
+
ensure
|
|
158
|
+
zio.close if zio
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @api private
|
|
163
|
+
def open_archive(context)
|
|
164
|
+
check_limits(open_native(context))
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Ziperu.max_entries / max_total_size, checked before any entry is
|
|
168
|
+
# read. An archive that fails is closed again.
|
|
169
|
+
def check_limits(native)
|
|
170
|
+
if (limit = ::Ziperu.max_entries) && native.entry_count > limit
|
|
171
|
+
raise LimitError.new('an entry count', native.entry_count, limit)
|
|
172
|
+
end
|
|
173
|
+
if (limit = ::Ziperu.max_total_size) && (total = native.total_size) > limit
|
|
174
|
+
raise LimitError.new('a declared total size', total, limit)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
native
|
|
178
|
+
rescue LimitError
|
|
179
|
+
native.close
|
|
180
|
+
raise
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def open_native(context)
|
|
184
|
+
return Native::Archive.open_path(context.to_s) unless context.respond_to?(:read)
|
|
185
|
+
|
|
186
|
+
# A StringIO's bytes are already in memory: snapshot them once rather
|
|
187
|
+
# than paying a method call per chunk (12x faster per entry).
|
|
188
|
+
return Native::Archive.open_bytes(context.string) if context.kind_of?(::StringIO)
|
|
189
|
+
|
|
190
|
+
context.binmode if context.respond_to?(:binmode)
|
|
191
|
+
if IOExtras.seekable?(context)
|
|
192
|
+
# Driven lazily through seek/read; nothing is copied up front.
|
|
193
|
+
Native::Archive.open_io(context)
|
|
194
|
+
else
|
|
195
|
+
# A pipe or socket: there is no way back to the central directory
|
|
196
|
+
# once the data has gone past, so take it all.
|
|
197
|
+
bytes = context.read.to_s.b
|
|
198
|
+
raise SplitArchiveError if split_signature?(bytes)
|
|
199
|
+
|
|
200
|
+
Native::Archive.open_bytes(bytes)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# The first part of a multi-part archive starts with a split-file
|
|
205
|
+
# signature.
|
|
206
|
+
#
|
|
207
|
+
# @api private
|
|
208
|
+
def split_archive?(context)
|
|
209
|
+
head =
|
|
210
|
+
if context.respond_to?(:read)
|
|
211
|
+
# A pipe cannot be peeked at; `open_archive` checks what it reads.
|
|
212
|
+
return false unless IOExtras.seekable?(context)
|
|
213
|
+
|
|
214
|
+
context.seek(0, ::IO::SEEK_SET)
|
|
215
|
+
context.read(4)
|
|
216
|
+
elsif ::File.file?(context.to_s)
|
|
217
|
+
::File.binread(context.to_s, 4)
|
|
218
|
+
end
|
|
219
|
+
split_signature?(head)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def split_signature?(head)
|
|
223
|
+
!head.nil? && head.bytesize >= 4 && head.unpack1('V') == SPLIT_FILE_SIGNATURE
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
protected
|
|
228
|
+
|
|
229
|
+
def open_entry # :nodoc:
|
|
230
|
+
entry = @current_entry
|
|
231
|
+
unless READABLE_COMPRESSION_METHODS.include?(entry.compression_method)
|
|
232
|
+
raise CompressionMethodError, entry.compression_method
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
if entry.encrypted? && @decrypter.nil?
|
|
236
|
+
raise Error, PASSWORD_REQUIRED_MESSAGE
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
password = @decrypter&.password&.b
|
|
240
|
+
@reader = wrap_errors do
|
|
241
|
+
if entry.index
|
|
242
|
+
@archive.open_entry(entry.index, password, @enforce_size)
|
|
243
|
+
else
|
|
244
|
+
@archive.open_entry_at(entry.local_header_offset, entry.size, entry.compressed_size,
|
|
245
|
+
entry.crc, password, @enforce_size)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
flush
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def produce_input(maxlen = CHUNK_SIZE) # :nodoc:
|
|
252
|
+
raise IOError, 'closed stream' if closed?
|
|
253
|
+
raise ArgumentError, "negative length #{maxlen} given" if maxlen&.negative?
|
|
254
|
+
return nil if @reader.nil?
|
|
255
|
+
|
|
256
|
+
wrap_errors { @reader.read(maxlen) }
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def input_finished? # :nodoc:
|
|
260
|
+
# A closed stream is not "at EOF": reading it raises, as with an IO.
|
|
261
|
+
return false if closed?
|
|
262
|
+
|
|
263
|
+
@reader.nil? || @reader.eof?
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
private
|
|
267
|
+
|
|
268
|
+
# Nil when no entry starts there, which reads as "no more entries".
|
|
269
|
+
def index_at_offset(offset)
|
|
270
|
+
return @records.index { |meta| meta[:header_offset] == offset } if @records
|
|
271
|
+
|
|
272
|
+
found = @archive.entries.find { |meta| meta[:header_offset] == offset }
|
|
273
|
+
found && found[:index]
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Closed by `close`, as opposed to never opened on an archive at all.
|
|
277
|
+
def closed?
|
|
278
|
+
@archive.nil? && @not_an_archive.nil?
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Re-raise the native core's errors as the rubyzip-shaped ones.
|
|
282
|
+
def wrap_errors
|
|
283
|
+
yield
|
|
284
|
+
rescue Native::SizeLimitError
|
|
285
|
+
raise EntrySizeError, @current_entry
|
|
286
|
+
rescue Native::PasswordError => e
|
|
287
|
+
# rubyzip verifies AES passwords up front but just inflates garbage for
|
|
288
|
+
# a wrong ZipCrypto one, so the exception classes differ.
|
|
289
|
+
raise Error, 'Bad password' if @decrypter.kind_of?(AESDecrypter)
|
|
290
|
+
|
|
291
|
+
raise DecompressionError, e
|
|
292
|
+
rescue Native::UnsupportedError => e
|
|
293
|
+
raise Error, e.message
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
require_relative 'fake_io'
|
|
6
|
+
|
|
7
|
+
module Ziperu
|
|
8
|
+
module IOExtras # :nodoc:
|
|
9
|
+
# Implements many of the convenience methods of IO such as gets, getc,
|
|
10
|
+
# read, readline and readlines. Depends on: input_finished?,
|
|
11
|
+
# produce_input and read.
|
|
12
|
+
module AbstractInputStream
|
|
13
|
+
include Enumerable
|
|
14
|
+
include FakeIO
|
|
15
|
+
|
|
16
|
+
def initialize(**opts)
|
|
17
|
+
super
|
|
18
|
+
@lineno = 0
|
|
19
|
+
@pos = 0
|
|
20
|
+
@output_buffer = +''.b
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_accessor :lineno
|
|
24
|
+
attr_reader :pos
|
|
25
|
+
|
|
26
|
+
def read(maxlen = nil, out_string = nil) # rubocop:disable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity
|
|
27
|
+
return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
|
|
28
|
+
|
|
29
|
+
tbuf = if @output_buffer.bytesize > 0
|
|
30
|
+
if maxlen && maxlen <= @output_buffer.bytesize
|
|
31
|
+
@output_buffer.slice!(0, maxlen)
|
|
32
|
+
else
|
|
33
|
+
maxlen -= @output_buffer.bytesize if maxlen
|
|
34
|
+
rbuf = produce_input(maxlen)
|
|
35
|
+
out = @output_buffer
|
|
36
|
+
out << rbuf if rbuf
|
|
37
|
+
@output_buffer = +''.b
|
|
38
|
+
out
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
produce_input(maxlen)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if tbuf.nil? || tbuf.empty?
|
|
45
|
+
return nil if maxlen&.positive?
|
|
46
|
+
|
|
47
|
+
return ''
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@pos += tbuf.length
|
|
51
|
+
|
|
52
|
+
if out_string.nil?
|
|
53
|
+
tbuf.force_encoding(@internal_encoding || @external_encoding)
|
|
54
|
+
else
|
|
55
|
+
encoding = out_string.encoding
|
|
56
|
+
out_string.replace(tbuf).force_encoding(encoding)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def readlines(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
|
|
61
|
+
each(sep, limit, chomp: chomp).to_a
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def gets(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
65
|
+
if sep.nil?
|
|
66
|
+
return nil if eof?
|
|
67
|
+
|
|
68
|
+
@lineno = @lineno.next
|
|
69
|
+
return read(limit)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if sep.respond_to?(:to_int)
|
|
73
|
+
limit = sep.to_int
|
|
74
|
+
sep = $INPUT_RECORD_SEPARATOR
|
|
75
|
+
elsif sep&.empty?
|
|
76
|
+
sep = "#{$INPUT_RECORD_SEPARATOR}#{$INPUT_RECORD_SEPARATOR}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
encoding = @internal_encoding || @external_encoding
|
|
80
|
+
|
|
81
|
+
buffer_index = 0
|
|
82
|
+
while (sep_index = @output_buffer.index(sep, buffer_index)).nil?
|
|
83
|
+
break if limit && @output_buffer.bytesize >= limit
|
|
84
|
+
|
|
85
|
+
if input_finished?
|
|
86
|
+
return nil if @output_buffer.empty?
|
|
87
|
+
|
|
88
|
+
@lineno = @lineno.next
|
|
89
|
+
@pos += @output_buffer.bytesize
|
|
90
|
+
return @output_buffer.slice!(0..).force_encoding(encoding)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
buffer_index = [buffer_index, @output_buffer.bytesize - sep.bytesize].max
|
|
94
|
+
@output_buffer << produce_input
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
limit ||= @output_buffer.bytesize
|
|
98
|
+
cut_index = sep_index ? [sep_index + sep.bytesize, limit].min : limit
|
|
99
|
+
@lineno = @lineno.next
|
|
100
|
+
@pos += cut_index
|
|
101
|
+
data = @output_buffer.slice!(0, cut_index)
|
|
102
|
+
data&.chomp!(sep) if chomp
|
|
103
|
+
data&.force_encoding(encoding)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def ungetc(byte) # :nodoc:
|
|
107
|
+
@output_buffer = byte.chr + @output_buffer
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def flush # :nodoc:
|
|
111
|
+
@output_buffer.slice!(0..)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def readline(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
|
|
115
|
+
raise EOFError if eof?
|
|
116
|
+
|
|
117
|
+
gets(sep, limit, chomp: chomp)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def each(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
|
|
121
|
+
return to_enum(:each, sep, limit, chomp: chomp) unless block_given?
|
|
122
|
+
|
|
123
|
+
while (line = gets(sep, limit, chomp: chomp))
|
|
124
|
+
yield line
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
alias each_line each
|
|
129
|
+
|
|
130
|
+
def eof?
|
|
131
|
+
@output_buffer.empty? && input_finished?
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
alias eof eof? # :nodoc:
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
require_relative 'fake_io'
|
|
6
|
+
|
|
7
|
+
module Ziperu
|
|
8
|
+
module IOExtras # :nodoc:
|
|
9
|
+
# Implements many of the output convenience methods of IO.
|
|
10
|
+
# relies on <<
|
|
11
|
+
module AbstractOutputStream # :nodoc:
|
|
12
|
+
include FakeIO
|
|
13
|
+
|
|
14
|
+
def write(data)
|
|
15
|
+
self << data
|
|
16
|
+
data.to_s.bytesize
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def print(*params)
|
|
20
|
+
self << params.join
|
|
21
|
+
|
|
22
|
+
# Deflate doesn't like `nil`s or empty strings!
|
|
23
|
+
unless $OUTPUT_RECORD_SEPARATOR.nil? || $OUTPUT_RECORD_SEPARATOR.empty?
|
|
24
|
+
self << $OUTPUT_RECORD_SEPARATOR
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def printf(a_format_string, *params)
|
|
31
|
+
self << format(a_format_string, *params)
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def putc(an_object)
|
|
36
|
+
self << case an_object
|
|
37
|
+
when Integer
|
|
38
|
+
an_object.chr
|
|
39
|
+
when String
|
|
40
|
+
an_object
|
|
41
|
+
else
|
|
42
|
+
raise TypeError, 'putc: Only Integer and String supported'
|
|
43
|
+
end
|
|
44
|
+
an_object
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def puts(*params)
|
|
48
|
+
params << "\n" if params.empty?
|
|
49
|
+
params.flatten.each do |element|
|
|
50
|
+
val = element.to_s
|
|
51
|
+
self << val
|
|
52
|
+
self << "\n" unless val[-1, 1] == "\n"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
module Ziperu
|
|
6
|
+
module IOExtras # :nodoc:
|
|
7
|
+
module FakeIO # :nodoc:
|
|
8
|
+
attr_reader :external_encoding, :internal_encoding
|
|
9
|
+
|
|
10
|
+
def initialize(**opts)
|
|
11
|
+
@internal_encoding = Encoding.find(opts[:internal_encoding]) if opts[:internal_encoding]
|
|
12
|
+
|
|
13
|
+
if opts[:encoding].kind_of?(String)
|
|
14
|
+
_ext, int = opts[:encoding].split(':', 2)
|
|
15
|
+
@internal_encoding = Encoding.find(int) unless @internal_encoding || int.nil?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@external_encoding = Encoding::ASCII_8BIT
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def set_encoding(ext_enc, int_enc = nil)
|
|
22
|
+
if ext_enc.kind_of?(String) && ext_enc.include?(':')
|
|
23
|
+
_ext_enc, int_enc = ext_enc.split(':', 2)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
@internal_encoding = Encoding.find(int_enc) if int_enc
|
|
28
|
+
rescue ArgumentError
|
|
29
|
+
warn "warning: Unsupported encoding #{int_enc} ignored"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@external_encoding = Encoding::ASCII_8BIT
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Implement kind_of? in order to pretend to be an IO object.
|
|
36
|
+
def kind_of?(object)
|
|
37
|
+
object == IO || super
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# For $INPUT_RECORD_SEPARATOR, the line-separator default in gets and friends.
|
|
4
|
+
require 'English'
|
|
5
|
+
|
|
6
|
+
require_relative 'ioextras/fake_io'
|
|
7
|
+
require_relative 'ioextras/abstract_input_stream'
|
|
8
|
+
require_relative 'ioextras/abstract_output_stream'
|
|
9
|
+
|
|
10
|
+
module Ziperu
|
|
11
|
+
# IO-like conveniences shared by the stream classes. Ported from rubyzip so
|
|
12
|
+
# that `gets`, `readline`, `each_line` and friends behave identically.
|
|
13
|
+
module IOExtras # :nodoc:
|
|
14
|
+
CHUNK_SIZE = 32_768
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# Whether `io` can be positioned: it answers `seek` and `tell` and a
|
|
18
|
+
# no-op seek succeeds (a pipe or socket raises).
|
|
19
|
+
def seekable?(io)
|
|
20
|
+
return false unless io.respond_to?(:seek) && io.respond_to?(:tell)
|
|
21
|
+
|
|
22
|
+
io.seek(0, ::IO::SEEK_CUR)
|
|
23
|
+
true
|
|
24
|
+
rescue SystemCallError, IOError
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def copy_stream(ostream, istream)
|
|
29
|
+
ostream.write(istream.read(CHUNK_SIZE, +'')) until istream.eof?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def copy_stream_n(ostream, istream, nbytes)
|
|
33
|
+
toread = nbytes
|
|
34
|
+
while toread > 0 && !istream.eof?
|
|
35
|
+
tr = [toread, CHUNK_SIZE].min
|
|
36
|
+
ostream.write(istream.read(tr, +''))
|
|
37
|
+
toread -= tr
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported verbatim from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
# Not on any ziperu code path: the Rust core compresses and decompresses.
|
|
5
|
+
# Kept so that code which references these classes keeps loading.
|
|
6
|
+
module Ziperu
|
|
7
|
+
class NullCompressor < Compressor # :nodoc:all
|
|
8
|
+
include Singleton
|
|
9
|
+
|
|
10
|
+
def <<(_data)
|
|
11
|
+
raise IOError, 'closed stream'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :size, :compressed_size
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
|
19
|
+
# rubyzip is free software; you can redistribute it and/or
|
|
20
|
+
# modify it under the terms of the ruby license.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported verbatim from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
# Not on any ziperu code path: the Rust core compresses and decompresses.
|
|
5
|
+
# Kept so that code which references these classes keeps loading.
|
|
6
|
+
module Ziperu
|
|
7
|
+
module NullDecompressor # :nodoc:all
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def read(_length = nil, _outbuf = nil)
|
|
11
|
+
nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def eof?
|
|
15
|
+
true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Alias for compatibility. Remove for version 4.
|
|
19
|
+
alias eof eof?
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
|
24
|
+
# rubyzip is free software; you can redistribute it and/or
|
|
25
|
+
# modify it under the terms of the ruby license.
|