omnizip 0.3.41 → 0.3.43
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 +63 -0
- data/CONTEXT.md +67 -0
- data/lib/omnizip/formats/iso.rb +6 -7
- data/lib/omnizip/formats/rar/decompressor.rb +11 -2
- data/lib/omnizip/formats/rar/reader.rb +20 -2
- data/lib/omnizip/formats/rar.rb +11 -10
- data/lib/omnizip/formats/seven_zip/reader.rb +41 -3
- 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: 436437db6697f7363305eda4e6a3e2b78b922aa12b3c43e29756efe187310b60
|
|
4
|
+
data.tar.gz: 6e0b17e143c50beae378cdb5b7902e773d888bca14183fcf83e61dfeabe56a5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1e20ae301093cacb8c582e1b554a678c72cefa6bf21095d129bebc14b8c4095a68a87da653ef5ea16cb65c00de9edd6ae7b0d92191e49c25e05438ceba264ab
|
|
7
|
+
data.tar.gz: a6ad938ab6bdfd75586bcef013ba056cc523681ef34449d03bc44daf18a591e6485b292b7c62bd17c72c13c20c7ff8c45caf4faf6735d5cff79a0c2f4a5a010b
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.42] - 2026-08-29
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- `Formats::Zip::Reader` is usable immediately: entries parse on
|
|
14
|
+
first use, so `extract_all`/listing no longer silently do nothing
|
|
15
|
+
when `#read` was not called first. `#read` remains for eager
|
|
16
|
+
loading.
|
|
17
|
+
- `Temp::ArchiveHelper` writes zips through the native
|
|
18
|
+
`Formats::Zip::Writer` instead of the rubyzip-compat
|
|
19
|
+
`Omnizip::Zip::File` seam (the Metadata subsystem remains on
|
|
20
|
+
compat deliberately as an in-place central-directory editor).
|
|
21
|
+
- The RAR extraction spill cache installs an `at_exit` sweeper on
|
|
22
|
+
first use, reclaiming spill directories at normal process exit.
|
|
23
|
+
- Module docs tell the truth: `Formats::Rar` claimed read-only with
|
|
24
|
+
external tools required; `Formats::Iso` claimed read-only while
|
|
25
|
+
carrying the verified writer. New `CONTEXT.md` records the domain
|
|
26
|
+
glossary (primary parser vs adapters, native vs external decode,
|
|
27
|
+
interop-verified vs Omnizip-internal codecs, handler routes, the
|
|
28
|
+
compat seam, bridges).
|
|
29
|
+
|
|
30
|
+
## [0.3.41] - 2026-08-29
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- One RAR parser. `Formats::Rar3::Reader` and `Formats::Rar5::Reader`
|
|
34
|
+
are adapters over the primary `Formats::Rar::Reader` — the only
|
|
35
|
+
parser verified against real WinRAR archives — replacing ~600
|
|
36
|
+
lines of duplicate parsing. Both legacy interfaces gain real-
|
|
37
|
+
archive fidelity for free (exact mtimes, backslash normalization,
|
|
38
|
+
directory detection, CRC fields). The legacy RAR5 writer — whose
|
|
39
|
+
output not even unrar could list (garbage names, 2038 dates) —
|
|
40
|
+
now bridges onto the spec-conformant primary writer; its archives
|
|
41
|
+
pass `unrar t`. The primary Header learned minimal archives (file
|
|
42
|
+
block first, no main header) for RAR4 and RAR5, restoring a legacy
|
|
43
|
+
capability in one place.
|
|
44
|
+
- Direct-seek extraction: the parser records each entry's
|
|
45
|
+
data_offset, so native extraction is one seek + read instead of
|
|
46
|
+
re-parsing the archive per entry. unrar-backed extraction spills
|
|
47
|
+
the archive once per archive path instead of extracting the whole
|
|
48
|
+
archive per entry (O(n^2) -> O(n)). Fixed a latent off-by-two in
|
|
49
|
+
the fallback block walk.
|
|
50
|
+
- Converter symmetry: zip -> 7z uses the native reader with the
|
|
51
|
+
same extract-to-temp/write-from-tree shape as 7z -> zip; internal
|
|
52
|
+
code no longer calls the rubyzip-compat `Omnizip::Zip::File` seam.
|
|
53
|
+
- `omnizip archive create` passes one flat options hash;
|
|
54
|
+
`Formats::Rar.create` applies the RAR4/RAR5-specific semantics
|
|
55
|
+
(the RAR4 writer's recovery guard no longer trips on explicit
|
|
56
|
+
false).
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
- Native-decode CRC guard referenced `Compression::DecompressionError`
|
|
60
|
+
which does not exist (the classes live under
|
|
61
|
+
`Compression::Dispatcher`); the NameError was silently masked by
|
|
62
|
+
the fallback rescue.
|
|
63
|
+
- Unicode-flagged RAR4 names can carry non-UTF-8 bytes;
|
|
64
|
+
String#tr raised and aborted the block walk under non-UTF-8
|
|
65
|
+
locales (CI runners). Names are scrubbed before normalization.
|
|
66
|
+
- Corrupt size fields drove unbounded reads: a garbage pack_size
|
|
67
|
+
attempted a multi-gigabyte read (NoMemoryError on CI) or an
|
|
68
|
+
overflowing seek (EINVAL on Linux, silently clamped on macOS).
|
|
69
|
+
Packed data is now seeked past — never allocated to skip — with
|
|
70
|
+
sizes bounds-checked against the archive length and corrupt skips
|
|
71
|
+
clamped to EOF so entries still list.
|
|
72
|
+
|
|
10
73
|
## [0.3.40] - 2026-08-29
|
|
11
74
|
|
|
12
75
|
### Fixed
|
data/CONTEXT.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
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 decode
|
|
23
|
+
|
|
24
|
+
- **Reader invariant — always usable** — every Reader
|
|
25
|
+
(`Formats::Zip`, `Formats::SevenZip`, `Formats::Rar`) parses on
|
|
26
|
+
first use; calling `#open` first is never required, only eager.
|
|
27
|
+
|
|
28
|
+
- **Native decode** — decompression implemented in Ruby inside
|
|
29
|
+
omnizip. RAR native decode is **CRC-gated**: the stored header CRC
|
|
30
|
+
must match the decoded bytes or the result is discarded.
|
|
31
|
+
- **External fallback** — delegating to an external tool (the unrar
|
|
32
|
+
command) when native decode cannot be trusted. RAR extraction
|
|
33
|
+
spills the archive once per archive path (the **extraction spill
|
|
34
|
+
cache**) and copies entries out of it.
|
|
35
|
+
|
|
36
|
+
## Writing and interop
|
|
37
|
+
|
|
38
|
+
- **Interop-verified** — output accepted by the official tool for
|
|
39
|
+
the format (unrar for RAR, 7zz for 7z/ISO, unzip for ZIP), checked
|
|
40
|
+
with `test` plus a byte-identical extraction round-trip.
|
|
41
|
+
- **Omnizip-internal codec** — a compression stream only omnizip can
|
|
42
|
+
decode (RAR methods above :store). Archives carrying them must be
|
|
43
|
+
labeled honestly; official tools fail loudly, never silently.
|
|
44
|
+
- **STORE** — the uncompressed method; the interoperability floor
|
|
45
|
+
for every archive writer in this library.
|
|
46
|
+
|
|
47
|
+
## Seams
|
|
48
|
+
|
|
49
|
+
- **Compat seam** — `Omnizip::Zip` (aliased into the global `Zip`
|
|
50
|
+
namespace by `omnizip/rubyzip_compat`): a rubyzip-compatible
|
|
51
|
+
layer for library consumers migrating off rubyzip. Internal code
|
|
52
|
+
must use the native `Formats::Zip` tree instead; the Metadata
|
|
53
|
+
subsystem is its one deliberate in-house consumer (in-place
|
|
54
|
+
central-directory editing).
|
|
55
|
+
- **Bridge** — an adapter that carries a file-based implementation
|
|
56
|
+
across to a non-file interface, spilling through a temporary file
|
|
57
|
+
(the RAR3/RAR5 legacy readers, `Buffer::SevenZipBridge`).
|
|
58
|
+
- **Primary parser** — the one parser per format family verified
|
|
59
|
+
against real archives (e.g. `Formats::Rar::Reader`). Legacy
|
|
60
|
+
interfaces are adapters over primary parsers, never parallel
|
|
61
|
+
implementations.
|
|
62
|
+
|
|
63
|
+
## In memory
|
|
64
|
+
|
|
65
|
+
- **Buffer** — `Omnizip::Buffer`: in-memory archive create/read for
|
|
66
|
+
ZIP (stream-based) and 7z (through the bridge). Format detection
|
|
67
|
+
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
|
|
@@ -12,7 +12,7 @@ module Omnizip
|
|
|
12
12
|
class Reader
|
|
13
13
|
include Omnizip::Formats::Rar::Constants
|
|
14
14
|
|
|
15
|
-
attr_reader :file_path, :header, :
|
|
15
|
+
attr_reader :file_path, :header, :archive_info,
|
|
16
16
|
:volume_manager
|
|
17
17
|
|
|
18
18
|
# Initialize reader with file path
|
|
@@ -25,6 +25,7 @@ module Omnizip
|
|
|
25
25
|
@archive_info = Models::RarArchive.new(file_path)
|
|
26
26
|
@volume_manager = VolumeManager.new(file_path)
|
|
27
27
|
@use_native = true # Prefer native decompression
|
|
28
|
+
@opened = false
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
# Open and parse RAR archive
|
|
@@ -34,14 +35,24 @@ module Omnizip
|
|
|
34
35
|
File.open(@file_path, "rb") do |io|
|
|
35
36
|
parse_archive(io)
|
|
36
37
|
end
|
|
38
|
+
@opened = true
|
|
37
39
|
self
|
|
38
40
|
end
|
|
39
41
|
|
|
42
|
+
# All entries, parsing on first use — a Reader is always
|
|
43
|
+
# usable; #open remains for eager loading
|
|
44
|
+
#
|
|
45
|
+
# @return [Array<Models::RarEntry>] File entries
|
|
46
|
+
def entries
|
|
47
|
+
ensure_open
|
|
48
|
+
@entries
|
|
49
|
+
end
|
|
50
|
+
|
|
40
51
|
# List all files in archive
|
|
41
52
|
#
|
|
42
53
|
# @return [Array<Models::RarEntry>] File entries
|
|
43
54
|
def list_files
|
|
44
|
-
|
|
55
|
+
entries
|
|
45
56
|
end
|
|
46
57
|
|
|
47
58
|
# Extract file to output path
|
|
@@ -51,6 +62,7 @@ module Omnizip
|
|
|
51
62
|
# @param password [String, nil] Optional password
|
|
52
63
|
# @raise [RuntimeError] if entry not found or extraction fails
|
|
53
64
|
def extract_entry(entry_name, output_path, password: nil)
|
|
65
|
+
ensure_open
|
|
54
66
|
entry = @entries.find { |e| e.name == entry_name }
|
|
55
67
|
raise "Entry not found: #{entry_name}" unless entry
|
|
56
68
|
|
|
@@ -79,6 +91,7 @@ module Omnizip
|
|
|
79
91
|
# @param password [String, nil] Optional password
|
|
80
92
|
# @raise [RuntimeError] on extraction error
|
|
81
93
|
def extract_all(output_dir, password: nil)
|
|
94
|
+
ensure_open
|
|
82
95
|
FileUtils.mkdir_p(output_dir)
|
|
83
96
|
|
|
84
97
|
# Use decompressor to extract all
|
|
@@ -126,6 +139,11 @@ module Omnizip
|
|
|
126
139
|
|
|
127
140
|
private
|
|
128
141
|
|
|
142
|
+
# Parse the archive once, on demand
|
|
143
|
+
def ensure_open
|
|
144
|
+
open unless @opened
|
|
145
|
+
end
|
|
146
|
+
|
|
129
147
|
# Parse RAR archive structure
|
|
130
148
|
#
|
|
131
149
|
# @param io [IO] Input stream
|
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"
|
|
@@ -28,6 +28,7 @@ module Omnizip
|
|
|
28
28
|
@offset = options[:offset] || 0
|
|
29
29
|
@solid_cache = {}
|
|
30
30
|
@solid_cache_extracted = {}
|
|
31
|
+
@opened = false
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
# Open and parse .7z archive
|
|
@@ -79,10 +80,12 @@ module Omnizip
|
|
|
79
80
|
@split_reader&.volumes || [@file_path]
|
|
80
81
|
end
|
|
81
82
|
|
|
82
|
-
# List all files in archive
|
|
83
|
+
# List all files in archive, parsing on first use — a Reader
|
|
84
|
+
# is always usable; #open remains for eager loading
|
|
83
85
|
#
|
|
84
86
|
# @return [Array<Models::FileEntry>] File entries
|
|
85
87
|
def list_files
|
|
88
|
+
ensure_open
|
|
86
89
|
@entries.reject { |e| e.name.nil? || e.name.empty? }
|
|
87
90
|
end
|
|
88
91
|
|
|
@@ -92,6 +95,7 @@ module Omnizip
|
|
|
92
95
|
# @param output_path [String] Destination path
|
|
93
96
|
# @raise [RuntimeError] if entry not found or extraction fails
|
|
94
97
|
def extract_entry(entry_name, output_path)
|
|
98
|
+
ensure_open
|
|
95
99
|
# Delegate to split reader if available
|
|
96
100
|
if @split_reader
|
|
97
101
|
@split_reader.extract_entry(entry_name, output_path)
|
|
@@ -132,6 +136,7 @@ module Omnizip
|
|
|
132
136
|
# @param output_dir [String] Destination directory
|
|
133
137
|
# @raise [RuntimeError] on extraction error
|
|
134
138
|
def extract_all(output_dir)
|
|
139
|
+
ensure_open
|
|
135
140
|
FileUtils.mkdir_p(output_dir)
|
|
136
141
|
|
|
137
142
|
real_out = File.realpath(output_dir)
|
|
@@ -187,6 +192,11 @@ module Omnizip
|
|
|
187
192
|
|
|
188
193
|
private
|
|
189
194
|
|
|
195
|
+
# Parse the archive once, on demand
|
|
196
|
+
def ensure_open
|
|
197
|
+
open unless @opened
|
|
198
|
+
end
|
|
199
|
+
|
|
190
200
|
# Parse .7z archive structure
|
|
191
201
|
#
|
|
192
202
|
# @param io [io] Input stream
|
|
@@ -236,7 +246,16 @@ module Omnizip
|
|
|
236
246
|
|
|
237
247
|
# Parse metadata
|
|
238
248
|
parser = Parser.new(next_header_data)
|
|
239
|
-
|
|
249
|
+
begin
|
|
250
|
+
@stream_info, @entries = parse_metadata(parser)
|
|
251
|
+
rescue StandardError => e
|
|
252
|
+
if @headers_decrypted
|
|
253
|
+
raise "Failed to decrypt headers: incorrect password " \
|
|
254
|
+
"or corrupted data (#{e.message})"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
raise
|
|
258
|
+
end
|
|
240
259
|
|
|
241
260
|
# Map entries to their folders/streams
|
|
242
261
|
map_entries_to_streams
|
|
@@ -257,11 +276,16 @@ module Omnizip
|
|
|
257
276
|
|
|
258
277
|
# Decrypt using password
|
|
259
278
|
encryptor = HeaderEncryptor.new(@password)
|
|
260
|
-
encryptor.decrypt(
|
|
279
|
+
decrypted = encryptor.decrypt(
|
|
261
280
|
@encrypted_header.encrypted_data,
|
|
262
281
|
@encrypted_header.salt,
|
|
263
282
|
@encrypted_header.iv,
|
|
264
283
|
)
|
|
284
|
+
# AES-CBC 'succeeds' with any key and yields garbage; wrong
|
|
285
|
+
# passwords surface as later header-parse failures, so mark
|
|
286
|
+
# the archive and translate those into the password error
|
|
287
|
+
@headers_decrypted = true
|
|
288
|
+
decrypted
|
|
265
289
|
rescue OpenSSL::Cipher::CipherError => e
|
|
266
290
|
raise "Failed to decrypt headers: incorrect password (#{e.message})"
|
|
267
291
|
end
|
|
@@ -393,6 +417,20 @@ module Omnizip
|
|
|
393
417
|
entry.folder_index = folder_idx
|
|
394
418
|
entry.file_index = i
|
|
395
419
|
entry.size = @stream_info.unpack_sizes[stream_idx] if @stream_info.unpack_sizes[stream_idx]
|
|
420
|
+
|
|
421
|
+
# Single-stream folders own their packed bytes outright,
|
|
422
|
+
# so the entry's compressed size is exact. Solid folders
|
|
423
|
+
# share packed bytes across streams — leave nil rather
|
|
424
|
+
# than fabricate a split.
|
|
425
|
+
num_in_folder = @stream_info.num_unpack_streams_in_folders[folder_idx]
|
|
426
|
+
if num_in_folder == 1
|
|
427
|
+
folder = @stream_info.folders[folder_idx]
|
|
428
|
+
packed = folder.pack_stream_indices.sum do |pi|
|
|
429
|
+
@stream_info.pack_sizes[pi] || 0
|
|
430
|
+
end
|
|
431
|
+
entry.compressed_size = packed
|
|
432
|
+
end
|
|
433
|
+
|
|
396
434
|
stream_idx += 1
|
|
397
435
|
end
|
|
398
436
|
end
|
|
@@ -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.43
|
|
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
|