omnizip 0.4.5 → 0.4.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a55c37209e986f7e4499e8a485163728e9c8f0dff506e1aa852672a9585653f
4
- data.tar.gz: ba0b5437026feed4c1fe4d0ab0146f868386ebe2284ec7b08b45c7260930d582
3
+ metadata.gz: 7dc6e02021b3915af5f65944e4d734bd3f95a5624b2a01c30100b6bbc36d5856
4
+ data.tar.gz: 998838c2bd3df94ced6617b9d199a0f2eab552e332d285bdc6819860fef50324
5
5
  SHA512:
6
- metadata.gz: 13d25032be26ccd096718846ac74126fd14ab3e2729d1e9ed366a3c431815b69c0817e33092f214eea4ed041e41e2eebe070ac92862bd54c70b0379017281637
7
- data.tar.gz: 8e68e9a4a0d7f489be9956baf6f2d18bb82d5cc0db123adaf689ab73aefca1dcb44c0172a8e774c0cbcfbcca2927c9f40b62da184678588e084642b3b86cd7eb
6
+ metadata.gz: f02b277ef180e076b5e0deb3847ed4cdf3f2a83abf829095779cdbfbdbc811891c8d9db9e000fb071c1d3bc164bd3cf4f031b8e19207b7d6e48232a21bf573dc
7
+ data.tar.gz: 4d20378f2973be45cc1cb691894b9c54916c1f5546d45a64a5143feaf8cabf0e053d8d604b8f30b6534d123ad12084021d9815b82945f78f5bfacd64f4daef96
data/CHANGELOG.md CHANGED
@@ -25,6 +25,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
25
25
  route decode through the Rust backend when it loads, each pinned by
26
26
  a cross-implementation differential. A Rust failure falls back to
27
27
  the Ruby core — auto mode is never worse than pure Ruby.
28
+ - Archive-level acceleration: `Implementations::Rust::Archive` binds
29
+ the cdylib's whole-archive surface (open, list entries, read entry)
30
+ over every multi-entry format — zip, tar, cpio, 7z, rar3/4/5, iso,
31
+ rpm, xar, ole, encrypted archives included. `ZipHandler#list` and
32
+ `#read_entry` route through it first (auto mode; any Rust error or
33
+ a missing cdylib falls back to the pure-Ruby path). The tier's
34
+ 600-mutant fuzz gate also caught a Rust decompression bomb
35
+ (truncated deflate streams decoded an endless phantom bit-stream;
36
+ fixed upstream in omnizip 0.21.108).
37
+ - `Library#bind` / `Library#take_buffer` seams the archive surface
38
+ rides (Fiddle symbol binding + copy-out-and-free).
28
39
 
29
40
  ### Fixed
30
41
  - RAR recovery-record repair fails loudly instead of pretending:
@@ -48,6 +48,14 @@ module Omnizip
48
48
  end
49
49
 
50
50
  def list(path, details: false, **_)
51
+ # Whole-archive tier (names-only path): the Rust archive
52
+ # surface lists any supported format; the Ruby reader stays
53
+ # the fallback and the details path's source.
54
+ unless details
55
+ tier_names = Omnizip::Backends.archive_entry_names(path)
56
+ return tier_names if tier_names
57
+ end
58
+
51
59
  reader = Formats::Zip::Reader.new(path)
52
60
  return reader.entries.map(&:filename) unless details
53
61
 
@@ -65,6 +73,9 @@ module Omnizip
65
73
  end
66
74
 
67
75
  def read_entry(path, entry_name)
76
+ tier = Omnizip::Backends.archive_read_entry(path, entry_name)
77
+ return tier unless tier.nil?
78
+
68
79
  Formats::Zip::Reader.new(path).read_entry(entry_name)
69
80
  end
70
81
 
@@ -94,6 +94,26 @@ module Omnizip
94
94
  end
95
95
  end
96
96
 
97
+ # Whole-archive tier helpers: nil means "Rust unavailable or
98
+ # entry not found — caller falls back to the Ruby reader".
99
+ def archive_entry_names(path)
100
+ Omnizip::Implementations::Rust::Archive.open(File.binread(path)) { |a| return a.entry_names }
101
+ rescue Omnizip::Implementations::Rust::Archive::LibraryMissing, Omnizip::Implementations::Rust::Error, StandardError
102
+ nil
103
+ end
104
+
105
+ # rubocop:disable-next Lint/ReturnInVoidContext
106
+ def archive_read_entry(path, entry_name)
107
+ Omnizip::Implementations::Rust::Archive.open(File.binread(path)) do |a|
108
+ idx = a.entry_names.index(entry_name)
109
+ return nil if idx.nil?
110
+
111
+ return a.read_entry(idx)
112
+ end
113
+ rescue Omnizip::Implementations::Rust::Archive::LibraryMissing, Omnizip::Implementations::Rust::Error, StandardError
114
+ nil
115
+ end
116
+
97
117
  private
98
118
 
99
119
  def backend_rust
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module Implementations
5
+ module Rust
6
+ # Whole-archive acceleration over the cdylib: open any supported
7
+ # format (auto-detected), list entries, read entry bytes. This is
8
+ # the archive-level tier surface — the Ruby core remains the
9
+ # fallback (the cdylib unavailable ⇒ these raise LibraryMissing
10
+ # and callers fall back).
11
+ class Archive
12
+ class LibraryMissing < StandardError; end
13
+
14
+ # Built lazily inside #initialize: the Fiddle::TYPE_* constants
15
+ # only resolve after a successful require, and Ruby 4.0 moved
16
+ # fiddle out of the default gems (same reason
17
+ # Library.function_table is a method, not a constant).
18
+ def initialize(data, password: nil)
19
+ lib = Library.instance
20
+ raise LibraryMissing, "omnizip-ffi cdylib not loaded" unless lib
21
+
22
+ @lib = lib
23
+ funcs = function_table
24
+ @funcs = funcs.each_with_object({}) do |(key, (name, args, ret)), h|
25
+ h[key] = lib.bind(name, args, ret)
26
+ end
27
+
28
+ pw = password&.to_s
29
+ pw_ptr = pw && !pw.empty? ? Fiddle::Pointer[pw] : nil
30
+ @handle = @funcs[:arch_open].call(Fiddle::Pointer[data], data.bytesize, pw_ptr)
31
+ return unless @handle.null?
32
+
33
+ raise Error, @lib.last_error
34
+ end
35
+
36
+ def entry_count
37
+ @funcs[:arch_count].call(@handle)
38
+ end
39
+
40
+ def entry_names
41
+ (0...entry_count).map do |i|
42
+ ptr = @funcs[:arch_entry_name].call(@handle, i)
43
+ next nil if ptr.null?
44
+
45
+ ptr.to_s
46
+ end
47
+ end
48
+
49
+ def entry_size(index)
50
+ @funcs[:arch_entry_size].call(@handle, index)
51
+ end
52
+
53
+ # Read one entry's uncompressed bytes.
54
+ #
55
+ # @return [String] binary
56
+ def read_entry(index)
57
+ out_len = [0].pack("Q")
58
+ ptr = @funcs[:arch_read_entry].call(@handle, index, out_len)
59
+ raise Error, @lib.last_error if ptr.null?
60
+
61
+ @lib.take_buffer(ptr, out_len.unpack1("Q"))
62
+ end
63
+
64
+ def close
65
+ return unless @handle
66
+
67
+ @funcs[:arch_close].call(@handle)
68
+ @handle = nil
69
+ end
70
+
71
+ def self.open(data, password: nil)
72
+ archive = new(data, password: password)
73
+ yield archive
74
+ ensure
75
+ archive&.close
76
+ end
77
+
78
+ private
79
+
80
+ def function_table
81
+ {
82
+ arch_open: ["ozip_arch_open", %i[voidp size_t voidp], Fiddle::TYPE_VOIDP],
83
+ arch_count: ["ozip_arch_count", [:voidp], Fiddle::TYPE_SIZE_T],
84
+ arch_entry_name: ["ozip_arch_entry_name", %i[voidp size_t], Fiddle::TYPE_VOIDP],
85
+ arch_entry_size: ["ozip_arch_entry_size", %i[voidp size_t], Fiddle::TYPE_VOIDP],
86
+ arch_read_entry: ["ozip_arch_read_entry", %i[voidp size_t voidp], Fiddle::TYPE_VOIDP],
87
+ arch_close: ["ozip_arch_close", [:voidp], Fiddle::TYPE_VOID],
88
+ }
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -86,7 +86,7 @@ module Omnizip
86
86
  funcs = function_table.each_with_object({}) do |(key, (name, args, ret)), h|
87
87
  h[key] = Fiddle::Function.new(handle[name], args, ret)
88
88
  end
89
- new(funcs)
89
+ new(funcs, handle)
90
90
  rescue StandardError => e # includes Fiddle::DLError
91
91
  warn "omnizip: rust backend unavailable (#{e.message}); using pure Ruby" if ENV["OMNIZIP_BACKEND"] == "rust"
92
92
  nil
@@ -120,8 +120,9 @@ module Omnizip
120
120
  end
121
121
  end
122
122
 
123
- def initialize(functions)
123
+ def initialize(functions, handle = nil)
124
124
  @functions = functions.freeze
125
+ @handle = handle
125
126
  end
126
127
 
127
128
  def compress(codec, data, level)
@@ -136,11 +137,31 @@ module Omnizip
136
137
  take(ptr, out_len.unpack1("Q"))
137
138
  end
138
139
 
140
+ # Bind one extra C symbol from the loaded cdylib (the archive
141
+ # surface uses this; Fiddle::Function is immutable once built).
142
+ def bind(symbol, args, ret)
143
+ handle = instance_handle
144
+ raise Error, "cdylib not loaded" if handle.nil?
145
+
146
+ Fiddle::Function.new(handle[symbol.to_s], args, ret)
147
+ end
148
+
139
149
  def last_error
140
150
  ptr = @functions[:last_error].call
141
151
  ptr.null? ? "unknown error" : ptr.to_s
142
152
  end
143
153
 
154
+ # The raw Fiddle::Handle (for extra symbol binds).
155
+ def instance_handle
156
+ @handle
157
+ end
158
+
159
+ # Copy out + free one returned buffer (public seam for the
160
+ # archive surface).
161
+ def take_buffer(ptr, len)
162
+ take(ptr, len)
163
+ end
164
+
144
165
  private
145
166
 
146
167
  def call(name, codec, input, input_len, a, out_len)
@@ -20,6 +20,7 @@ module Omnizip
20
20
  # fallback on any Rust error.
21
21
  module Rust
22
22
  autoload :Library, "omnizip/implementations/rust/library"
23
+ autoload :Archive, "omnizip/implementations/rust/archive"
23
24
  end
24
25
  end
25
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.4.5"
4
+ VERSION = "0.4.6"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omnizip
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-20 00:00:00.000000000 Z
11
+ date: 2026-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -601,6 +601,7 @@ files:
601
601
  - lib/omnizip/implementations/base/lzma_encoder_base.rb
602
602
  - lib/omnizip/implementations/base/state_machine_base.rb
603
603
  - lib/omnizip/implementations/rust.rb
604
+ - lib/omnizip/implementations/rust/archive.rb
604
605
  - lib/omnizip/implementations/rust/library.rb
605
606
  - lib/omnizip/implementations/seven_zip.rb
606
607
  - lib/omnizip/implementations/seven_zip/lzma.rb