omnizip 0.3.20 → 0.3.21

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.refactor/00-overview.md +2 -2
  3. data/TODO.refactor/07-respond-to-replacement.md +225 -79
  4. data/lib/omnizip/algorithm.rb +2 -2
  5. data/lib/omnizip/algorithms/bzip2.rb +1 -0
  6. data/lib/omnizip/algorithms/deflate.rb +1 -0
  7. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +3 -3
  8. data/lib/omnizip/algorithms/lzma.rb +11 -5
  9. data/lib/omnizip/algorithms/lzma2/encoder.rb +2 -0
  10. data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +2 -0
  11. data/lib/omnizip/algorithms/lzma2.rb +2 -0
  12. data/lib/omnizip/algorithms/ppmd7.rb +2 -0
  13. data/lib/omnizip/algorithms/ppmd_base.rb +2 -0
  14. data/lib/omnizip/algorithms/registration.rb +1 -1
  15. data/lib/omnizip/algorithms/zstandard/encoder.rb +57 -18
  16. data/lib/omnizip/algorithms/zstandard/match_finder.rb +9 -5
  17. data/lib/omnizip/algorithms/zstandard.rb +1 -0
  18. data/lib/omnizip/chunked/writer.rb +3 -0
  19. data/lib/omnizip/commands/archive_extract_command.rb +3 -1
  20. data/lib/omnizip/commands/archive_list_command.rb +4 -3
  21. data/lib/omnizip/commands/profile_show_command.rb +2 -0
  22. data/lib/omnizip/convenience.rb +2 -0
  23. data/lib/omnizip/extraction/filter_chain.rb +4 -0
  24. data/lib/omnizip/extraction/selective_extractor.rb +7 -0
  25. data/lib/omnizip/file_type.rb +4 -0
  26. data/lib/omnizip/formats/ole.rb +2 -0
  27. data/lib/omnizip/formats/rar/decompressor.rb +1 -0
  28. data/lib/omnizip/formats/rar/license_validator.rb +2 -0
  29. data/lib/omnizip/formats/rar/reader.rb +1 -2
  30. data/lib/omnizip/formats/rpm.rb +1 -0
  31. data/lib/omnizip/formats/seven_zip/writer.rb +8 -0
  32. data/lib/omnizip/formats/tar/reader.rb +2 -0
  33. data/lib/omnizip/formats/xar/reader.rb +2 -0
  34. data/lib/omnizip/formats/xar/writer.rb +1 -0
  35. data/lib/omnizip/formats/xz.rb +2 -0
  36. data/lib/omnizip/formats/xz_impl/block_decoder.rb +3 -0
  37. data/lib/omnizip/formats/xz_impl/stream_decoder.rb +8 -0
  38. data/lib/omnizip/formats/zip/reader.rb +2 -0
  39. data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +1 -0
  40. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +4 -9
  41. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +1 -16
  42. data/lib/omnizip/io/buffered_input.rb +1 -0
  43. data/lib/omnizip/io/buffered_output.rb +1 -0
  44. data/lib/omnizip/io/source.rb +4 -0
  45. data/lib/omnizip/io/stream_manager.rb +3 -1
  46. data/lib/omnizip/link_handler.rb +2 -0
  47. data/lib/omnizip/metadata/metadata_validator.rb +2 -0
  48. data/lib/omnizip/models/filter_config.rb +2 -0
  49. data/lib/omnizip/version.rb +1 -1
  50. data/lib/omnizip/zip/file.rb +1 -1
  51. data/lib/omnizip/zip/input_stream.rb +2 -1
  52. metadata +1 -1
@@ -178,15 +178,18 @@ module Omnizip
178
178
  limit = block_end - mm
179
179
 
180
180
  while ip < limit
181
- match = if ldm
181
+ match = if ldm || lazy.positive?
182
+ # Lazy levels share the LDM loop's rep0 fast-path
183
+ # (with backward extension): without it, levels
184
+ # 6+ measured worse than the greedy default level
185
+ # because every rep-offset match paid full offset
186
+ # coding.
182
187
  find_match_ldm(src, ip, ms, mm, limit, anchor,
183
188
  seq_store.rep_offsets[0], ldm,
184
189
  max_distance)
185
- elsif lazy.zero?
190
+ else
186
191
  find_greedy_match(src, ip, ms, mm, limit, anchor,
187
192
  seq_store.rep_offsets[0])
188
- else
189
- find_best_match(src, ip, ms, mm, limit)
190
193
  end
191
194
  if match
192
195
  dist, len = match
@@ -247,7 +250,8 @@ module Omnizip
247
250
  ldm, max_distance)
248
251
  return nil unless dist
249
252
 
250
- [dist, len, ip]
253
+ back = backward_extension(src, ip, anchor, ip - dist)
254
+ [dist, len + back, ip - back]
251
255
  end
252
256
 
253
257
  # Greedy-path match search (port of the Rust
@@ -116,6 +116,7 @@ module Omnizip
116
116
  level = case options
117
117
  when nil then nil
118
118
  when Hash then options[:level]
119
+ # allowed: options is a public parameter; any object with #level is honored
119
120
  else options.level if options.respond_to?(:level)
120
121
  end
121
122
 
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+ require "stringio"
5
+
3
6
  module Omnizip
4
7
  module Chunked
5
8
  # Write large files incrementally in chunks
@@ -16,6 +16,8 @@
16
16
  # See the COPYING file for the complete text of the license.
17
17
  #
18
18
 
19
+ require "fileutils"
20
+
19
21
  module Omnizip
20
22
  module Commands
21
23
  # Command to extract .7z archives to a directory.
@@ -222,7 +224,7 @@ module Omnizip
222
224
 
223
225
  extracted.size
224
226
  ensure
225
- archive&.close if archive.respond_to?(:close)
227
+ archive.close if archive.is_a?(Omnizip::Zip::File)
226
228
  end
227
229
 
228
230
  def extract_gzip(archive_file, output_dir, verbose)
@@ -69,7 +69,7 @@ module Omnizip
69
69
  entries = if patterns || excludes
70
70
  filter_entries(archive, patterns, excludes)
71
71
  else
72
- archive.respond_to?(:entries) ? archive.entries : archive.to_a
72
+ archive.entries
73
73
  end
74
74
 
75
75
  if count_only
@@ -91,6 +91,8 @@ module Omnizip
91
91
  rescue StandardError => e
92
92
  raise Omnizip::CompressionError,
93
93
  "Failed to list archive: #{e.message}"
94
+ ensure
95
+ archive.close if archive.is_a?(Omnizip::Zip::File)
94
96
  end
95
97
 
96
98
  def filter_entries(archive, patterns, excludes)
@@ -102,8 +104,7 @@ module Omnizip
102
104
  # Add exclude patterns
103
105
  excludes&.each { |pattern| filter.exclude_pattern(pattern) }
104
106
 
105
- entries = archive.respond_to?(:entries) ? archive.entries : archive.to_a
106
- filter.filter(entries)
107
+ filter.filter(archive.entries)
107
108
  end
108
109
 
109
110
  def display_simple_listing_filtered(entries)
@@ -33,6 +33,8 @@ module Omnizip
33
33
  puts "Solid: #{profile.solid}"
34
34
  puts "Description: #{profile.description}"
35
35
 
36
+ # ProfileRegistry accepts any CompressionProfile subclass.
37
+ # allowed: a registered subclass may expose base_profile
36
38
  return unless profile.respond_to?(:base_profile) && profile.base_profile
37
39
 
38
40
  puts "Based on: #{profile.base_profile.name}"
@@ -123,6 +123,7 @@ module Omnizip
123
123
  end
124
124
 
125
125
  handler = Omnizip::ArchiveHandler.for(format)
126
+ # allowed: handler is a registered duck; missing add_entry raises below
126
127
  if handler.respond_to?(:add_entry)
127
128
  handler.add_entry(archive_path, entry_name, source_path)
128
129
  else
@@ -142,6 +143,7 @@ module Omnizip
142
143
  def remove_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT)
143
144
  require_archive!(archive_path)
144
145
  handler = Omnizip::ArchiveHandler.for(format)
146
+ # allowed: handler is a registered duck; missing remove_entry raises below
145
147
  unless handler.respond_to?(:remove_entry)
146
148
  raise Omnizip::UnsupportedFormatError,
147
149
  "Format #{format.inspect} does not support removing entries"
@@ -159,10 +159,14 @@ module Omnizip
159
159
  entry
160
160
  else
161
161
  # Try common filename methods
162
+ # Narrowing this changes which files a filter matches.
163
+ # allowed: entries may be foreign objects
162
164
  if entry.respond_to?(:name)
163
165
  entry.name
166
+ # allowed: entries may be foreign objects
164
167
  elsif entry.respond_to?(:path)
165
168
  entry.path
169
+ # allowed: entries may be foreign objects
166
170
  elsif entry.respond_to?(:filename)
167
171
  entry.filename
168
172
  else
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Omnizip
4
6
  module Extraction
5
7
  # Coordinates selective extraction from archives
@@ -120,8 +122,10 @@ module Omnizip
120
122
  #
121
123
  # @return [Array] All entries
122
124
  def list_all
125
+ # allowed: public API takes any archive object, including foreign ones
123
126
  if @archive.respond_to?(:entries)
124
127
  @archive.entries
128
+ # allowed: public API takes any Enumerable archive
125
129
  elsif @archive.respond_to?(:each)
126
130
  @archive.to_a
127
131
  else
@@ -150,10 +154,13 @@ module Omnizip
150
154
  # @param entry [Object] Entry to read
151
155
  # @return [String] Entry content
152
156
  def read_entry_content(entry)
157
+ # allowed: entries come from a caller-supplied archive
153
158
  if entry.respond_to?(:read)
154
159
  entry.read
160
+ # allowed: rubyzip-style entries expose get_input_stream, not read
155
161
  elsif entry.respond_to?(:get_input_stream)
156
162
  entry.get_input_stream.read
163
+ # allowed: some archives read entry content from the archive
157
164
  elsif @archive.respond_to?(:read)
158
165
  @archive.read(entry)
159
166
  else
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "marcel"
4
+ require "stringio"
4
5
 
5
6
  module Omnizip
6
7
  # File type detection module using Marcel for MIME type detection
@@ -95,16 +96,19 @@ module Omnizip
95
96
  return nil unless io
96
97
 
97
98
  # Save current position
99
+ # allowed: caller-supplied IO; only saved when the stream tracks one
98
100
  original_pos = io.pos if io.respond_to?(:pos)
99
101
 
100
102
  mime_type = Marcel::MimeType.for(io, name: filename)
101
103
 
102
104
  # Restore position
105
+ # allowed: caller-supplied IO capability probe
103
106
  io.seek(original_pos) if original_pos && io.respond_to?(:seek)
104
107
 
105
108
  mime_type
106
109
  rescue StandardError
107
110
  # Attempt to restore position even on error
111
+ # allowed: caller-supplied IO capability probe
108
112
  io.seek(original_pos) if original_pos && io.respond_to?(:seek)
109
113
  nil
110
114
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Omnizip
4
6
  module Formats
5
7
  # OLE compound document format support
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "English"
4
+ require "fileutils"
4
5
  require "tmpdir"
5
6
  module Omnizip
6
7
  module Formats
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Omnizip
4
6
  module Formats
5
7
  module Rar
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "stringio"
4
5
 
5
6
  module Omnizip
6
7
  module Formats
@@ -287,8 +288,6 @@ module Omnizip
287
288
  # @param entry [Models::RarEntry] File entry
288
289
  # @return [StringIO] Compressed data stream
289
290
  def read_compressed_data(entry)
290
- require "stringio"
291
-
292
291
  # Find the entry's data offset in the archive
293
292
  File.open(@file_path, "rb") do |io|
294
293
  # Skip signature and headers
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
3
4
  require "stringio"
4
5
  require "tempfile"
5
6
 
@@ -490,7 +490,15 @@ num_files)
490
490
 
491
491
  def compress_with_lzma2(data)
492
492
  # Use 7-Zip SDK LZMA2 encoder for 7-Zip format
493
+ #
494
+ # The encoder dictionary is sized to the data but never
495
+ # exceeds the size announced in the coder properties
496
+ # (options default 8MB): the decoder allocates its window
497
+ # from the announced size, so a larger encoder dictionary
498
+ # could emit matches reading outside the decoder window.
499
+ announced = @options[:dict_size] || (8 * 1024 * 1024)
493
500
  dict_size = [4096, data.bytesize].max
501
+ dict_size = announced if announced < dict_size
494
502
 
495
503
  encoder = Omnizip::Implementations::SevenZip::LZMA2::Encoder.new(
496
504
  dict_size: dict_size,
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Omnizip
4
6
  module Formats
5
7
  module Tar
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "zlib"
4
4
  require "digest"
5
+ require "fileutils"
6
+ require "stringio"
5
7
 
6
8
  module Omnizip
7
9
  module Formats
@@ -3,6 +3,7 @@
3
3
  require "zlib"
4
4
  require "digest"
5
5
  require "fileutils"
6
+ require "stringio"
6
7
 
7
8
  module Omnizip
8
9
  module Formats
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "stringio"
4
+
3
5
  module Omnizip
4
6
  module Formats
5
7
  # XZ compression format
@@ -77,6 +77,7 @@ module Omnizip
77
77
  end
78
78
 
79
79
  def set_encoding(enc)
80
+ # allowed: caller-supplied IO; String streams lack set_encoding
80
81
  @stream.set_encoding(enc) if @stream.respond_to?(:set_encoding)
81
82
  end
82
83
  end
@@ -110,7 +111,9 @@ module Omnizip
110
111
 
111
112
  if ENV["XZ_BLOCK_DEBUG"]
112
113
  warn "DEBUG: decode - compressed_size=#{compressed_size.inspect}, check_type=#{@check_type}"
114
+ # allowed: debug output under XZ_BLOCK_DEBUG
113
115
  warn "DEBUG: @input.class=#{@input.class}, @input.respond_to?(:pos)=#{@input.respond_to?(:pos)}"
116
+ # allowed: debug output under XZ_BLOCK_DEBUG
114
117
  pos = @input.respond_to?(:pos) ? @input.pos : "N/A"
115
118
  warn "DEBUG: @input.pos=#{pos}"
116
119
  end
@@ -46,6 +46,7 @@ module Omnizip
46
46
 
47
47
  # Store original input and file size for backward_size validation (if available)
48
48
  original_input = input
49
+ # allowed: caller-supplied IO; only sized streams get validated
49
50
  original_file_size = input.size if input.respond_to?(:size)
50
51
 
51
52
  output, block_count, final_input, block_sizes = decode_blocks(input,
@@ -57,6 +58,7 @@ module Omnizip
57
58
  # multiples of four bytes...If the stored value does not match the real size of
58
59
  # the Index field, the decoder MUST indicate an error."
59
60
  # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/stream_decoder.c
61
+ # allowed: caller-supplied IO; footer check needs seeking
60
62
  if original_input.respond_to?(:seek) && original_file_size&.positive?
61
63
  # Use original input and file size for validation
62
64
  validate_backward_size_from_footer(original_input,
@@ -138,6 +140,7 @@ module Omnizip
138
140
  # @param byte [Integer] Byte to restore
139
141
  # @raise [RuntimeError] If IO doesn't support ungetbyte
140
142
  def self.restore_byte(input, byte)
143
+ # allowed: caller-supplied IO; FormatError follows when unsupported
141
144
  return input.ungetbyte(byte) if input.respond_to?(:ungetbyte)
142
145
 
143
146
  raise FormatError,
@@ -227,6 +230,7 @@ module Omnizip
227
230
  # @param check_type [Symbol] Expected checksum type
228
231
  # @param index_size [Integer, nil] Actual index size for backward_size validation
229
232
  def self.verify_footer_if_seekable(input, check_type, index_size = nil)
233
+ # allowed: caller-supplied IO; skipped on non-seekable streams
230
234
  return unless input.respond_to?(:seek) && input.respond_to?(:size) && input.size
231
235
 
232
236
  original_pos = input.pos
@@ -290,6 +294,7 @@ module Omnizip
290
294
  # @param input [IO] Input stream
291
295
  # @raise [FormatError] If there's invalid trailing data
292
296
  def self.verify_no_trailing_data(input)
297
+ # allowed: caller-supplied IO; needs byte-level positioning
293
298
  return unless input.respond_to?(:pos) && input.respond_to?(:getbyte)
294
299
 
295
300
  # Skip stream padding (null bytes)
@@ -304,6 +309,7 @@ module Omnizip
304
309
  else
305
310
  # Non-zero byte found - this should be a new stream or it's invalid
306
311
  # Restore the byte and check if it's a valid stream header
312
+ # allowed: caller-supplied IO capability probe
307
313
  input.ungetbyte(byte) if input.respond_to?(:ungetbyte)
308
314
 
309
315
  # Stream padding must be a multiple of 4 bytes
@@ -345,6 +351,7 @@ module Omnizip
345
351
  end
346
352
 
347
353
  # Restore the bytes we read
354
+ # allowed: caller-supplied IO capability probe
348
355
  if input.respond_to?(:ungetbyte)
349
356
  potential_header.reverse_each do |b|
350
357
  input.ungetbyte(b)
@@ -381,6 +388,7 @@ module Omnizip
381
388
  # @raise [FormatError] If backward_size points to invalid position
382
389
  def self.validate_backward_size_from_footer(input, file_size,
383
390
  _index_size)
391
+ # allowed: caller-supplied IO; skipped on non-seekable streams
384
392
  return unless input.respond_to?(:seek)
385
393
  return if file_size.nil? || file_size.zero?
386
394
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Omnizip
4
6
  module Formats
5
7
  module Zip
@@ -121,6 +121,7 @@ module Omnizip
121
121
  # For raw mode, return actual decode bytes (excluding flush padding)
122
122
  if @raw_mode
123
123
  [@output.string, @range_encoder.bytes_for_decode]
124
+ # allowed: caller-supplied output; any #string sink buffers
124
125
  elsif @output.respond_to?(:string)
125
126
  # For File output, just return bytes written (don't try to read back)
126
127
  # For StringIO, return the string and its size
@@ -416,16 +416,11 @@ module Omnizip
416
416
 
417
417
  if @lzma2_debug
418
418
  warn "DEBUG: decompress_lzma_chunk - After set_input, checking range_decoder..."
419
- # Check if the decoder has a range_decoder variable
420
- if @lzma_decoder.respond_to?(:range_decoder)
421
- range_decoder = @lzma_decoder.range_decoder
422
- if range_decoder
423
- warn " range_decoder exists: code=0x#{range_decoder.code.to_s(16)}, range=0x#{range_decoder.range.to_s(16)}, init_bytes_remaining=#{range_decoder.init_bytes_remaining}"
424
- else
425
- warn " range_decoder is nil"
426
- end
419
+ range_decoder = @lzma_decoder.range_decoder
420
+ if range_decoder
421
+ warn " range_decoder exists: code=0x#{range_decoder.code.to_s(16)}, range=0x#{range_decoder.range.to_s(16)}, init_bytes_remaining=#{range_decoder.init_bytes_remaining}"
427
422
  else
428
- warn " @range_decoder not defined yet"
423
+ warn " range_decoder is nil"
429
424
  end
430
425
  end
431
426
  else
@@ -33,19 +33,6 @@ module Omnizip
33
33
  #
34
34
  # Ported from XZ Utils liblzma/lzma2_encoder.c
35
35
  #
36
- # Compatibility helper for Ruby 3.0-3.1 where String#byteslice doesn't exist
37
- module StringCompat
38
- if "".respond_to?(:byteslice)
39
- def self.byteslice(string, start, length)
40
- string.byteslice(start, length)
41
- end
42
- else
43
- def self.byteslice(string, start, length)
44
- string.bytes[start, length]&.pack("C*") || ""
45
- end
46
- end
47
- end
48
-
49
36
  # Constants
50
37
  UINT32_MAX = 0xFFFFFFFF
51
38
  REPS = 4
@@ -364,11 +351,9 @@ module Omnizip
364
351
 
365
352
  # Write to output stream
366
353
  if out_pos.value.positive?
367
- # Use StringCompat.byteslice for Ruby 3.0-3.1 compatibility
368
354
  # Ruby's [] operator has a bug with null bytes that can return extra bytes
369
355
  # See: https://bugs.ruby-lang.org/issues/15985
370
- output.write(StringCompat.byteslice(temp_buffer, 0,
371
- out_pos.value))
356
+ output.write(temp_buffer.byteslice(0, out_pos.value))
372
357
  end
373
358
 
374
359
  # Return the number of bytes written
@@ -76,6 +76,7 @@ module Omnizip
76
76
  #
77
77
  # @return [void]
78
78
  def close
79
+ # allowed: caller-supplied source may not be closeable
79
80
  @source.close if @source.respond_to?(:close)
80
81
  end
81
82
 
@@ -84,6 +84,7 @@ module Omnizip
84
84
  # @return [void]
85
85
  def close
86
86
  flush
87
+ # allowed: caller-supplied destination may not be closeable
87
88
  @destination.close if @destination.respond_to?(:close)
88
89
  end
89
90
 
@@ -31,6 +31,7 @@ module Omnizip
31
31
  when ::IO, ::StringIO, ::Tempfile then new(input)
32
32
  when String then StringSource.new(input)
33
33
  else
34
+ # allowed: boundary validation; duck-typing ends at this adapter
34
35
  unless input.respond_to?(:read)
35
36
  raise ArgumentError,
36
37
  "Cannot adapt #{input.inspect} to Omnizip::IO::Source"
@@ -56,6 +57,7 @@ module Omnizip
56
57
  end
57
58
 
58
59
  def close
60
+ # allowed: wrapped object may be a read-only duck with no close
59
61
  @io.close if @io.respond_to?(:close)
60
62
  end
61
63
 
@@ -98,6 +100,7 @@ module Omnizip
98
100
  when ::IO, ::StringIO, ::Tempfile then new(output)
99
101
  when String then PathSink.new(output)
100
102
  else
103
+ # allowed: boundary validation; duck-typing ends at this adapter
101
104
  unless output.respond_to?(:write)
102
105
  raise ArgumentError,
103
106
  "Cannot adapt #{output.inspect} to Omnizip::IO::Sink"
@@ -116,6 +119,7 @@ module Omnizip
116
119
  end
117
120
 
118
121
  def close
122
+ # allowed: wrapped object may be a write-only duck with no close
119
123
  @io.close if @io.respond_to?(:close)
120
124
  end
121
125
 
@@ -71,13 +71,14 @@ module Omnizip
71
71
  #
72
72
  # @return [void]
73
73
  def close
74
- @source.close if @owned && @source.respond_to?(:close)
74
+ @source.close if @owned
75
75
  end
76
76
 
77
77
  # Check if source is at end.
78
78
  #
79
79
  # @return [Boolean] True if at EOF
80
80
  def eof?
81
+ # allowed: write-only sources have no eof?
81
82
  @source.eof? if @source.respond_to?(:eof?)
82
83
  end
83
84
 
@@ -103,6 +104,7 @@ module Omnizip
103
104
  end
104
105
 
105
106
  def validate_io_interface(source)
107
+ # allowed: boundary validation of a caller-supplied IO-like object
106
108
  if source.respond_to?(:read) || source.respond_to?(:write)
107
109
  source
108
110
  else
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Omnizip
4
6
  # Handles symbolic and hard link operations with platform detection
5
7
  module LinkHandler
@@ -9,6 +9,8 @@ module Omnizip
9
9
  # @raise [ArgumentError] If metadata is invalid
10
10
  def validate_entry(metadata)
11
11
  validate_comment(metadata.comment) if metadata.comment
12
+ # validate_entry is public API.
13
+ # allowed: metadata need only expose what it validates
12
14
  validate_time(metadata.mtime) if metadata.respond_to?(:mtime)
13
15
  validate_permissions(metadata.unix_permissions) if metadata.unix_permissions
14
16
 
@@ -94,6 +94,8 @@ module Omnizip
94
94
  # @raise [NotImplementedError] If filter doesn't support id_for_format
95
95
  def id_for_format(format)
96
96
  filter = filter_instance
97
+ # FilterRegistry accepts any class, with no inheritance check.
98
+ # allowed: keeps NotImplementedError for filters without the method
97
99
  if filter.respond_to?(:id_for_format)
98
100
  filter.id_for_format(format)
99
101
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.3.20"
4
+ VERSION = "0.3.21"
5
5
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "stringio"
4
5
 
5
6
  module Omnizip
6
7
  module Zip
@@ -102,7 +103,6 @@ module Omnizip
102
103
  content = read_entry_data(entry)
103
104
 
104
105
  if block
105
- require "stringio"
106
106
  StringIO.open(content, "rb", &block)
107
107
  else
108
108
  content
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "stringio"
4
+
3
5
  module Omnizip
4
6
  module Zip
5
7
  # Rubyzip-compatible InputStream class
@@ -88,7 +90,6 @@ module Omnizip
88
90
  @current_entry.compression_method,
89
91
  @current_entry.size,
90
92
  )
91
- require "stringio"
92
93
  @current_entry_io = StringIO.new(decompressed, "rb")
93
94
  end
94
95
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omnizip
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.20
4
+ version: 0.3.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.