omnizip 0.3.41 → 0.3.42
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 +43 -0
- data/CONTEXT.md +63 -0
- data/lib/omnizip/formats/iso.rb +6 -7
- data/lib/omnizip/formats/rar/decompressor.rb +11 -2
- data/lib/omnizip/formats/rar.rb +11 -10
- data/lib/omnizip/formats/zip/reader.rb +13 -1
- data/lib/omnizip/temp.rb +6 -3
- data/lib/omnizip/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 060755f6c69bca46bcb999d0d36471045ae3d13e260cfa3c73601a8d440b1634
|
|
4
|
+
data.tar.gz: b524b77c579c824009821ca5d1bdb47327cfc55f3ebd1dab4ea8574d1ec3c631
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 237f14e0c479844a473d1830e956882a0d250f6c733522bcd9f3d36b7ba10b474b4564902791aecd52948ad330b82bb0bb7e61dd97db607bf3dcd85ab3dc847b
|
|
7
|
+
data.tar.gz: cfe4637c8ad1223ccb8b9735fc1494d61937b636f3d79dd7fbd2c1f59f6f35eb38686152ee56732f0cb29cfbf3c12f7ddfb6dae5bd6c146b8d833fca5315a00a
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.41] - 2026-08-29
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- One RAR parser. `Formats::Rar3::Reader` and `Formats::Rar5::Reader`
|
|
14
|
+
are adapters over the primary `Formats::Rar::Reader` — the only
|
|
15
|
+
parser verified against real WinRAR archives — replacing ~600
|
|
16
|
+
lines of duplicate parsing. Both legacy interfaces gain real-
|
|
17
|
+
archive fidelity for free (exact mtimes, backslash normalization,
|
|
18
|
+
directory detection, CRC fields). The legacy RAR5 writer — whose
|
|
19
|
+
output not even unrar could list (garbage names, 2038 dates) —
|
|
20
|
+
now bridges onto the spec-conformant primary writer; its archives
|
|
21
|
+
pass `unrar t`. The primary Header learned minimal archives (file
|
|
22
|
+
block first, no main header) for RAR4 and RAR5, restoring a legacy
|
|
23
|
+
capability in one place.
|
|
24
|
+
- Direct-seek extraction: the parser records each entry's
|
|
25
|
+
data_offset, so native extraction is one seek + read instead of
|
|
26
|
+
re-parsing the archive per entry. unrar-backed extraction spills
|
|
27
|
+
the archive once per archive path instead of extracting the whole
|
|
28
|
+
archive per entry (O(n^2) -> O(n)). Fixed a latent off-by-two in
|
|
29
|
+
the fallback block walk.
|
|
30
|
+
- Converter symmetry: zip -> 7z uses the native reader with the
|
|
31
|
+
same extract-to-temp/write-from-tree shape as 7z -> zip; internal
|
|
32
|
+
code no longer calls the rubyzip-compat `Omnizip::Zip::File` seam.
|
|
33
|
+
- `omnizip archive create` passes one flat options hash;
|
|
34
|
+
`Formats::Rar.create` applies the RAR4/RAR5-specific semantics
|
|
35
|
+
(the RAR4 writer's recovery guard no longer trips on explicit
|
|
36
|
+
false).
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- Native-decode CRC guard referenced `Compression::DecompressionError`
|
|
40
|
+
which does not exist (the classes live under
|
|
41
|
+
`Compression::Dispatcher`); the NameError was silently masked by
|
|
42
|
+
the fallback rescue.
|
|
43
|
+
- Unicode-flagged RAR4 names can carry non-UTF-8 bytes;
|
|
44
|
+
String#tr raised and aborted the block walk under non-UTF-8
|
|
45
|
+
locales (CI runners). Names are scrubbed before normalization.
|
|
46
|
+
- Corrupt size fields drove unbounded reads: a garbage pack_size
|
|
47
|
+
attempted a multi-gigabyte read (NoMemoryError on CI) or an
|
|
48
|
+
overflowing seek (EINVAL on Linux, silently clamped on macOS).
|
|
49
|
+
Packed data is now seeked past — never allocated to skip — with
|
|
50
|
+
sizes bounds-checked against the archive length and corrupt skips
|
|
51
|
+
clamped to EOF so entries still list.
|
|
52
|
+
|
|
10
53
|
## [0.3.40] - 2026-08-29
|
|
11
54
|
|
|
12
55
|
### Fixed
|
data/CONTEXT.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Domain Glossary
|
|
2
|
+
|
|
3
|
+
Names for the concepts that keep coming up in omnizip. Use these
|
|
4
|
+
words in code, docs, and reviews; when a term sharpens, update it
|
|
5
|
+
here.
|
|
6
|
+
|
|
7
|
+
## Archive access
|
|
8
|
+
|
|
9
|
+
- **Archive facade** — `Omnizip::Archive`: the format-neutral entry
|
|
10
|
+
point. `create` yields a Builder (add_file/add_directory/add_data),
|
|
11
|
+
`open` yields a ReaderSession (entries/read/extract/extract_all).
|
|
12
|
+
- **ArchiveHandler** — per-format adapter registered in the handler
|
|
13
|
+
registry (create/extract_to/list/read_entry) plus its extension
|
|
14
|
+
route. The facade and the convenience layer dispatch through
|
|
15
|
+
handlers, never through format classes directly.
|
|
16
|
+
- **Extension route** — mapping from a file extension to a format
|
|
17
|
+
symbol. Writable routes (`ARCHIVE_FORMAT_EXTENSIONS`) may create
|
|
18
|
+
archives; read routes (`READ_ARCHIVE_FORMAT_EXTENSIONS`) only
|
|
19
|
+
extract/list/read. `.rar`, `.cpio`, `.iso` are read-only routes;
|
|
20
|
+
creating them raises truthfully.
|
|
21
|
+
|
|
22
|
+
## Reading and decoding
|
|
23
|
+
|
|
24
|
+
- **Native decode** — decompression implemented in Ruby inside
|
|
25
|
+
omnizip. RAR native decode is **CRC-gated**: the stored header CRC
|
|
26
|
+
must match the decoded bytes or the result is discarded.
|
|
27
|
+
- **External fallback** — delegating to an external tool (the unrar
|
|
28
|
+
command) when native decode cannot be trusted. RAR extraction
|
|
29
|
+
spills the archive once per archive path (the **extraction spill
|
|
30
|
+
cache**) and copies entries out of it.
|
|
31
|
+
|
|
32
|
+
## Writing and interop
|
|
33
|
+
|
|
34
|
+
- **Interop-verified** — output accepted by the official tool for
|
|
35
|
+
the format (unrar for RAR, 7zz for 7z/ISO, unzip for ZIP), checked
|
|
36
|
+
with `test` plus a byte-identical extraction round-trip.
|
|
37
|
+
- **Omnizip-internal codec** — a compression stream only omnizip can
|
|
38
|
+
decode (RAR methods above :store). Archives carrying them must be
|
|
39
|
+
labeled honestly; official tools fail loudly, never silently.
|
|
40
|
+
- **STORE** — the uncompressed method; the interoperability floor
|
|
41
|
+
for every archive writer in this library.
|
|
42
|
+
|
|
43
|
+
## Seams
|
|
44
|
+
|
|
45
|
+
- **Compat seam** — `Omnizip::Zip` (aliased into the global `Zip`
|
|
46
|
+
namespace by `omnizip/rubyzip_compat`): a rubyzip-compatible
|
|
47
|
+
layer for library consumers migrating off rubyzip. Internal code
|
|
48
|
+
must use the native `Formats::Zip` tree instead; the Metadata
|
|
49
|
+
subsystem is its one deliberate in-house consumer (in-place
|
|
50
|
+
central-directory editing).
|
|
51
|
+
- **Bridge** — an adapter that carries a file-based implementation
|
|
52
|
+
across to a non-file interface, spilling through a temporary file
|
|
53
|
+
(the RAR3/RAR5 legacy readers, `Buffer::SevenZipBridge`).
|
|
54
|
+
- **Primary parser** — the one parser per format family verified
|
|
55
|
+
against real archives (e.g. `Formats::Rar::Reader`). Legacy
|
|
56
|
+
interfaces are adapters over primary parsers, never parallel
|
|
57
|
+
implementations.
|
|
58
|
+
|
|
59
|
+
## In memory
|
|
60
|
+
|
|
61
|
+
- **Buffer** — `Omnizip::Buffer`: in-memory archive create/read for
|
|
62
|
+
ZIP (stream-based) and 7z (through the bridge). Format detection
|
|
63
|
+
compares binary magic literals.
|
data/lib/omnizip/formats/iso.rb
CHANGED
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
module Omnizip
|
|
4
4
|
module Formats
|
|
5
5
|
# ISO 9660 CD-ROM filesystem format support
|
|
6
|
-
# Provides read-only access to ISO images
|
|
7
6
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# -
|
|
12
|
-
#
|
|
13
|
-
#
|
|
7
|
+
# Reads and writes ISO 9660 images: Primary Volume Descriptor
|
|
8
|
+
# parsing, directory traversal, file extraction, and image
|
|
9
|
+
# creation whose directory records point at allocated extents
|
|
10
|
+
# (verified against 7-Zip). Rock Ridge and Joliet are NOT
|
|
11
|
+
# implemented — the writer's options stay false rather than
|
|
12
|
+
# advertising extensions readers would misparse.
|
|
14
13
|
module Iso
|
|
15
14
|
# Nested classes - autoloaded
|
|
16
15
|
autoload :Reader, "omnizip/formats/iso/reader"
|
|
@@ -258,6 +258,7 @@ module Omnizip
|
|
|
258
258
|
raise "Command entry extraction failed: #{entry_name}"
|
|
259
259
|
end
|
|
260
260
|
extract_cache[cache_key] = cached_dir
|
|
261
|
+
install_exit_sweeper
|
|
261
262
|
end
|
|
262
263
|
|
|
263
264
|
source = File.join(cached_dir, entry_name)
|
|
@@ -265,8 +266,8 @@ module Omnizip
|
|
|
265
266
|
end
|
|
266
267
|
|
|
267
268
|
# Extracted-archive cache: archive path => spill directory.
|
|
268
|
-
# Cleared by .clear_extract_cache
|
|
269
|
-
#
|
|
269
|
+
# Cleared by .clear_extract_cache!; an at_exit sweeper
|
|
270
|
+
# reclaims everything at normal process termination.
|
|
270
271
|
def extract_cache
|
|
271
272
|
@extract_cache ||= {}
|
|
272
273
|
end
|
|
@@ -277,6 +278,14 @@ module Omnizip
|
|
|
277
278
|
extract_cache.clear
|
|
278
279
|
end
|
|
279
280
|
|
|
281
|
+
# Reclaim spill directories when the process exits normally
|
|
282
|
+
def install_exit_sweeper
|
|
283
|
+
return if @exit_sweeper_installed
|
|
284
|
+
|
|
285
|
+
at_exit { clear_extract_cache! }
|
|
286
|
+
@exit_sweeper_installed = true
|
|
287
|
+
end
|
|
288
|
+
|
|
280
289
|
# Build extract command
|
|
281
290
|
def build_extract_command(archive_path, output_dir, password)
|
|
282
291
|
# Array form: no shell involved, so paths and passwords
|
data/lib/omnizip/formats/rar.rb
CHANGED
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
module Omnizip
|
|
4
4
|
module Formats
|
|
5
5
|
# RAR archive format support
|
|
6
|
-
# Provides read-only access to RAR archives (single and multi-volume)
|
|
7
6
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# -
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
7
|
+
# - Formats::Rar::Reader — the primary parser, verified against
|
|
8
|
+
# real WinRAR archives: listing, directories, mtimes, and
|
|
9
|
+
# extraction (native STORE decode with stored-CRC verification;
|
|
10
|
+
# foreign compressed streams fall back to the unrar command
|
|
11
|
+
# after the CRC check rejects them)
|
|
12
|
+
# - Formats::Rar::Writer (RAR4) and Formats::Rar::Rar5::Writer —
|
|
13
|
+
# STORE output is unrar-verified; compressed methods produce
|
|
14
|
+
# Omnizip-internal streams (see the README interop table)
|
|
15
|
+
# - Formats::Rar3 / Formats::Rar5 — legacy interfaces, thin
|
|
16
|
+
# adapters over the primary reader and the RAR5 writer
|
|
17
|
+
# - Multi-volume archives, encryption (RAR5), recovery records
|
|
17
18
|
module Rar
|
|
18
19
|
# Nested classes - autoloaded
|
|
19
20
|
autoload :Constants, "omnizip/formats/rar/constants"
|
|
@@ -9,7 +9,7 @@ module Omnizip
|
|
|
9
9
|
class Reader
|
|
10
10
|
include Omnizip::Formats::Zip::Constants
|
|
11
11
|
|
|
12
|
-
attr_reader :file_path
|
|
12
|
+
attr_reader :file_path
|
|
13
13
|
|
|
14
14
|
def initialize(file_path)
|
|
15
15
|
@file_path = file_path
|
|
@@ -36,6 +36,13 @@ module Omnizip
|
|
|
36
36
|
self
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# All entries, parsing on first use — a Reader is usable
|
|
40
|
+
# immediately; #read remains for explicit eager loading
|
|
41
|
+
def entries
|
|
42
|
+
ensure_read
|
|
43
|
+
@entries
|
|
44
|
+
end
|
|
45
|
+
|
|
39
46
|
# Extract all files to a directory
|
|
40
47
|
def extract_all(output_dir, preserve_links: true,
|
|
41
48
|
dereference_links: false)
|
|
@@ -163,6 +170,11 @@ dereference_links: false)
|
|
|
163
170
|
|
|
164
171
|
private
|
|
165
172
|
|
|
173
|
+
# Parse the central directory once, on demand
|
|
174
|
+
def ensure_read
|
|
175
|
+
read if @entries.empty? && @central_directory.empty?
|
|
176
|
+
end
|
|
177
|
+
|
|
166
178
|
# Read central directory entries
|
|
167
179
|
def read_central_directory(io, eocd)
|
|
168
180
|
io.seek(eocd.central_directory_offset, ::IO::SEEK_SET)
|
data/lib/omnizip/temp.rb
CHANGED
|
@@ -129,9 +129,12 @@ module Omnizip
|
|
|
129
129
|
|
|
130
130
|
case format
|
|
131
131
|
when :zip
|
|
132
|
-
Omnizip::Zip
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
# Native writer — the rubyzip-compat Omnizip::Zip seam is
|
|
133
|
+
# for library consumers migrating from rubyzip, not for
|
|
134
|
+
# internal use
|
|
135
|
+
writer = Omnizip::Formats::Zip::Writer.new(path)
|
|
136
|
+
writer.add_data(name, data.to_s)
|
|
137
|
+
writer.write
|
|
135
138
|
end
|
|
136
139
|
end
|
|
137
140
|
end
|
data/lib/omnizip/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.3.42
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -115,6 +115,7 @@ files:
|
|
|
115
115
|
- ".rubocop.yml"
|
|
116
116
|
- ".rubocop_todo.yml"
|
|
117
117
|
- CHANGELOG.md
|
|
118
|
+
- CONTEXT.md
|
|
118
119
|
- COPYING
|
|
119
120
|
- Gemfile
|
|
120
121
|
- LICENSE
|