ziperu 0.1.1-arm-linux-musl

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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +24 -0
  3. data/LICENSE-rubyzip.txt +24 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +276 -0
  6. data/lib/rubyzip.rb +4 -0
  7. data/lib/zip/central_directory.rb +5 -0
  8. data/lib/zip/compressor.rb +5 -0
  9. data/lib/zip/constants.rb +5 -0
  10. data/lib/zip/crypto/aes_encryption.rb +5 -0
  11. data/lib/zip/crypto/decrypted_io.rb +5 -0
  12. data/lib/zip/crypto/encryption.rb +5 -0
  13. data/lib/zip/crypto/null_encryption.rb +5 -0
  14. data/lib/zip/crypto/traditional_encryption.rb +5 -0
  15. data/lib/zip/crypto.rb +5 -0
  16. data/lib/zip/decompressor.rb +5 -0
  17. data/lib/zip/deflater.rb +5 -0
  18. data/lib/zip/dirtyable.rb +5 -0
  19. data/lib/zip/dos_time.rb +5 -0
  20. data/lib/zip/entry.rb +5 -0
  21. data/lib/zip/entry_set.rb +5 -0
  22. data/lib/zip/errors.rb +5 -0
  23. data/lib/zip/extra_field/aes.rb +5 -0
  24. data/lib/zip/extra_field/generic.rb +5 -0
  25. data/lib/zip/extra_field/ntfs.rb +5 -0
  26. data/lib/zip/extra_field/old_unix.rb +5 -0
  27. data/lib/zip/extra_field/universal_time.rb +5 -0
  28. data/lib/zip/extra_field/unix.rb +5 -0
  29. data/lib/zip/extra_field/unknown.rb +5 -0
  30. data/lib/zip/extra_field/zip64.rb +5 -0
  31. data/lib/zip/extra_field.rb +5 -0
  32. data/lib/zip/file.rb +5 -0
  33. data/lib/zip/file_split.rb +5 -0
  34. data/lib/zip/filesystem/dir.rb +5 -0
  35. data/lib/zip/filesystem/directory_iterator.rb +5 -0
  36. data/lib/zip/filesystem/file.rb +5 -0
  37. data/lib/zip/filesystem/file_stat.rb +5 -0
  38. data/lib/zip/filesystem/zip_file_name_mapper.rb +5 -0
  39. data/lib/zip/filesystem.rb +5 -0
  40. data/lib/zip/inflater.rb +5 -0
  41. data/lib/zip/input_stream.rb +5 -0
  42. data/lib/zip/ioextras/abstract_input_stream.rb +5 -0
  43. data/lib/zip/ioextras/abstract_output_stream.rb +5 -0
  44. data/lib/zip/ioextras/fake_io.rb +5 -0
  45. data/lib/zip/ioextras.rb +5 -0
  46. data/lib/zip/null_compressor.rb +5 -0
  47. data/lib/zip/null_decompressor.rb +5 -0
  48. data/lib/zip/null_input_stream.rb +5 -0
  49. data/lib/zip/output_stream.rb +5 -0
  50. data/lib/zip/pass_thru_compressor.rb +5 -0
  51. data/lib/zip/pass_thru_decompressor.rb +5 -0
  52. data/lib/zip/streamable_directory.rb +5 -0
  53. data/lib/zip/streamable_stream.rb +5 -0
  54. data/lib/zip/version.rb +5 -0
  55. data/lib/zip.rb +19 -0
  56. data/lib/ziperu/3.1/ziperu.so +0 -0
  57. data/lib/ziperu/3.2/ziperu.so +0 -0
  58. data/lib/ziperu/3.3/ziperu.so +0 -0
  59. data/lib/ziperu/3.4/ziperu.so +0 -0
  60. data/lib/ziperu/4.0/ziperu.so +0 -0
  61. data/lib/ziperu/central_directory.rb +136 -0
  62. data/lib/ziperu/compressor.rb +14 -0
  63. data/lib/ziperu/constants.rb +148 -0
  64. data/lib/ziperu/crypto.rb +72 -0
  65. data/lib/ziperu/decompressor.rb +35 -0
  66. data/lib/ziperu/deflater.rb +41 -0
  67. data/lib/ziperu/dirtyable.rb +48 -0
  68. data/lib/ziperu/dos_time.rb +87 -0
  69. data/lib/ziperu/entry.rb +760 -0
  70. data/lib/ziperu/entry_set.rb +147 -0
  71. data/lib/ziperu/errors.rb +154 -0
  72. data/lib/ziperu/extra_field/aes.rb +52 -0
  73. data/lib/ziperu/extra_field/generic.rb +45 -0
  74. data/lib/ziperu/extra_field/ntfs.rb +98 -0
  75. data/lib/ziperu/extra_field/old_unix.rb +52 -0
  76. data/lib/ziperu/extra_field/universal_time.rb +81 -0
  77. data/lib/ziperu/extra_field/unix.rb +45 -0
  78. data/lib/ziperu/extra_field/unknown.rb +37 -0
  79. data/lib/ziperu/extra_field/zip64.rb +86 -0
  80. data/lib/ziperu/extra_field.rb +109 -0
  81. data/lib/ziperu/file.rb +614 -0
  82. data/lib/ziperu/file_split.rb +93 -0
  83. data/lib/ziperu/filesystem/dir.rb +88 -0
  84. data/lib/ziperu/filesystem/directory_iterator.rb +50 -0
  85. data/lib/ziperu/filesystem/file.rb +265 -0
  86. data/lib/ziperu/filesystem/file_stat.rb +112 -0
  87. data/lib/ziperu/filesystem/zip_file_name_mapper.rb +83 -0
  88. data/lib/ziperu/filesystem.rb +76 -0
  89. data/lib/ziperu/inflater.rb +60 -0
  90. data/lib/ziperu/input_stream.rb +296 -0
  91. data/lib/ziperu/ioextras/abstract_input_stream.rb +137 -0
  92. data/lib/ziperu/ioextras/abstract_output_stream.rb +57 -0
  93. data/lib/ziperu/ioextras/fake_io.rb +41 -0
  94. data/lib/ziperu/ioextras.rb +42 -0
  95. data/lib/ziperu/null_compressor.rb +20 -0
  96. data/lib/ziperu/null_decompressor.rb +25 -0
  97. data/lib/ziperu/null_input_stream.rb +55 -0
  98. data/lib/ziperu/output_stream.rb +223 -0
  99. data/lib/ziperu/pass_thru_compressor.rb +29 -0
  100. data/lib/ziperu/pass_thru_decompressor.rb +37 -0
  101. data/lib/ziperu/streamable_directory.rb +15 -0
  102. data/lib/ziperu/streamable_stream.rb +59 -0
  103. data/lib/ziperu/version.rb +7 -0
  104. data/lib/ziperu.rb +116 -0
  105. 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.