omnizip 0.3.37 → 0.3.39
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 +78 -9
- data/README.adoc +108 -140
- data/config/formats/rar3_spec.yml +1 -0
- data/docs/guides/archive-formats/gzip-format.adoc +2 -4
- data/docs/guides/archive-formats/ole-format.adoc +54 -219
- data/docs/guides/archive-formats/rar5.adoc +15 -15
- data/docs/guides/archive-formats/rpm-format.adoc +48 -189
- data/docs/reference/api/overview.adoc +16 -17
- data/docs/troubleshooting/index.adoc +1 -4
- data/lib/omnizip/archive_handler.rb +2 -0
- data/lib/omnizip/archive_handlers/cpio_handler.rb +47 -0
- data/lib/omnizip/archive_handlers/iso_handler.rb +54 -0
- data/lib/omnizip/archive_handlers.rb +2 -0
- data/lib/omnizip/cli.rb +25 -0
- data/lib/omnizip/convenience.rb +6 -4
- data/lib/omnizip/formats/iso/directory_builder.rb +24 -0
- data/lib/omnizip/formats/iso/reader.rb +2 -2
- data/lib/omnizip/formats/iso/writer.rb +20 -4
- data/lib/omnizip/formats/iso.rb +3 -1
- data/lib/omnizip/formats/rar/block_parser.rb +18 -8
- data/lib/omnizip/formats/rar/constants.rb +4 -0
- data/lib/omnizip/formats/rar/decompressor.rb +12 -8
- data/lib/omnizip/formats/rar/header.rb +7 -15
- data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +5 -3
- data/lib/omnizip/formats/rar/rar5/header.rb +50 -33
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +17 -34
- data/lib/omnizip/formats/rar/rar5/vint.rb +25 -37
- data/lib/omnizip/formats/rar/rar5/writer.rb +50 -14
- data/lib/omnizip/formats/rar/reader.rb +18 -14
- data/lib/omnizip/formats/rar/writer.rb +121 -136
- data/lib/omnizip/formats/rar.rb +5 -11
- data/lib/omnizip/formats/rar3/reader.rb +11 -50
- data/lib/omnizip/formats/rar3/writer.rb +13 -10
- data/lib/omnizip/formats/rar3.rb +15 -0
- data/lib/omnizip/formats/rar5/reader.rb +8 -10
- data/lib/omnizip/formats/rar5.rb +15 -0
- data/lib/omnizip/formats/seven_zip/parser.rb +22 -7
- data/lib/omnizip/formats/seven_zip/writer.rb +78 -19
- data/lib/omnizip/formats.rb +2 -0
- data/lib/omnizip/version.rb +1 -1
- data/readme-docs/api-usage.adoc +5 -4
- data/readme-docs/architecture.adoc +5 -4
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f86f08dba1d67ec9a22053ee355dda5d461c42d29b45134ad75a1192584d0a5
|
|
4
|
+
data.tar.gz: 31d78c577804b43509bfea9a8bc9fd5edfad4ac149752cabb0eef87eafebbf56
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6ee4bb68ede247277420ec455130140a8be8e1f417581f1c3f996cab48d34202ae26631862ef88ef301fb712c649f72100805c6d553afce51f2705747e68e28
|
|
7
|
+
data.tar.gz: b7f3567b7f8d3c7679bbd7563fd9fabf666aa3c1fbfdff38afd0e9e2dc9034e1f78428b710f2ff72518e45eef956eeb61a679c27df6b00e2a8e81cbbc0b5eed1
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,79 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.38] - 2026-08-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `.cpio` and `.iso` read routing: `extract_archive`/`list_archive`/
|
|
14
|
+
`read_from_archive`/`Archive.open` operate on CPIO archives and ISO
|
|
15
|
+
9660 images through new read-only handlers, mirroring the RAR
|
|
16
|
+
routing (creation keeps raising truthfully).
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- 7z archives with explicit directory entries (or zero-byte
|
|
20
|
+
entries) were structurally invalid: the writer never emitted the
|
|
21
|
+
kEmptyStream/kEmptyFile properties, hardcoded 0x20 attributes
|
|
22
|
+
over each entry's real attributes, and its kSubStreamsInfo
|
|
23
|
+
digest/size arrays counted directory entries as substreams. 7-Zip
|
|
24
|
+
rejects such archives with "Headers Error" (and our own reader
|
|
25
|
+
crashed extracting directory entries). Verified against 7zz
|
|
26
|
+
ground truth (`-mhc=off` dumps): kEmptyStream/kEmptyFile carry
|
|
27
|
+
RAW bit vectors with no all-defined marker — the parser expected
|
|
28
|
+
that marker everywhere, so it now reads both layouts correctly
|
|
29
|
+
(which also fixes reading 7-Zip-created archives containing empty
|
|
30
|
+
files).
|
|
31
|
+
- RAR5 vint encoding was not the spec encoding: multi-byte values
|
|
32
|
+
(any size >= 128, extra-area sizes, dictionary sizes) were written
|
|
33
|
+
in a byte-swapped form no reader decodes — archives with more
|
|
34
|
+
than ~127 bytes of content were corrupt for unrar AND for our own
|
|
35
|
+
spec-conformant parser. All round-trips now verified against
|
|
36
|
+
unrar, including encrypted STORE, multi-file, and multi-volume
|
|
37
|
+
archives (volume flags were the previously documented "no
|
|
38
|
+
write-side reference" gap: Main archive flags 0x0001/0x0002 and
|
|
39
|
+
end-of-archive 0x0001 are written per the RAR 5.0 specification).
|
|
40
|
+
- RAR5 file headers: a "mystery vint" not in the spec shifted every
|
|
41
|
+
field; the compression method was written into the version bits;
|
|
42
|
+
the file-attribute value chmods extracted files unreadable. Now:
|
|
43
|
+
spec field order, method in bits 8-10 with dictionary bits
|
|
44
|
+
11-15, Unix attribute 0o100644.
|
|
45
|
+
- RAR5 encrypted writing now persists the salt/IV as the file
|
|
46
|
+
encryption extra record (spec type 0x01) — archives it wrote
|
|
47
|
+
before could not be decrypted by anything (unrar reported "All
|
|
48
|
+
OK" only because no CRC was stored).
|
|
49
|
+
- RAR5 `:lzma`/`:lzss` compression advertised method bits for a
|
|
50
|
+
stream that nothing can decode (the encoder is not
|
|
51
|
+
official-RAR-compatible; `unrar` extracts empty files silently).
|
|
52
|
+
`Lzss.available?` is now honest, so writers fall back to STORE
|
|
53
|
+
with a warning; solid mode falls back to independent STORE
|
|
54
|
+
entries.
|
|
55
|
+
- ISO writing: directory records pointed every file at sector 0
|
|
56
|
+
(allocated extents were never copied onto the tree nodes); the
|
|
57
|
+
volume descriptor declared 100 sectors regardless of actual
|
|
58
|
+
size; `add_directory` entries were not marked as directories;
|
|
59
|
+
Rock Ridge and Joliet were advertised by default without being
|
|
60
|
+
implemented (the cloned SVD made 7-Zip read ASCII names as
|
|
61
|
+
UCS-2); `Reader#extract_all` looked entries up by bare name
|
|
62
|
+
instead of full path; `Formats::Iso.list` returned the Reader
|
|
63
|
+
instead of entries. Images now extract byte-identically through
|
|
64
|
+
7zz, including nested directories.
|
|
65
|
+
- CLI: `omnizip <command> --help` failed with an arity error on
|
|
66
|
+
every command (Thor does not route `--help` on subcommands by
|
|
67
|
+
itself). A dispatch hook now prints per-command help.
|
|
68
|
+
- `Omnizip::Formats::Rar::Rar5::MainHeader/FileHeader` were
|
|
69
|
+
unreferenceable unless another file happened to load header.rb
|
|
70
|
+
first (missing autoload entries); three Rar5 autoloads pointed at
|
|
71
|
+
files that never existed.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
- Docs: the OLE, RPM, GZIP, RAR5 and API-overview guides now use
|
|
75
|
+
the real APIs (`Formats::Ole.list/read/info`, `Formats::Rpm.*`,
|
|
76
|
+
`Gzip.compress_stream`, `Formats::Zip::Reader#read`, correct
|
|
77
|
+
error class names); fictional methods (`open_stream`, `root`,
|
|
78
|
+
`extract_to`, `extract_files`, `extract_payload`,
|
|
79
|
+
`Rar::Rar5::Reader`, scriptlets/signature accessors) removed.
|
|
80
|
+
|
|
81
|
+
## [0.3.37] - 2026-08-28
|
|
82
|
+
|
|
10
83
|
### Added
|
|
11
84
|
- `Omnizip::Archive` — the block-style facade the guides have
|
|
12
85
|
documented since the docs were written (it never existed; ~50
|
|
@@ -143,15 +216,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
143
216
|
been pinned to :copy) and replaced the match-encoding TODO with a
|
|
144
217
|
wire-level round-trip verification of the distance-8/length
|
|
145
218
|
pattern through the LZMA2 decoder. No code under lib/ changes.
|
|
146
|
-
|
|
147
|
-
## [0.3.30] - 2026-08-27
|
|
148
|
-
|
|
149
|
-
### Verified
|
|
150
|
-
- LZIP interop with the real `lzip` CLI (1.26): our members pass
|
|
151
|
-
`lzip -t`/`-dc` and the CLI's members decode through
|
|
152
|
-
`Formats::Lzip` (spec skips where the CLI is absent).
|
|
153
|
-
|
|
154
|
-
### Changed
|
|
155
219
|
- The remaining lib/ TODO markers are resolved as documented design
|
|
156
220
|
decisions rather than dangling work: RAR5 volume extras and
|
|
157
221
|
EndHeader volume flags (no write-side reference exists;
|
|
@@ -165,6 +229,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
165
229
|
the salt/IV header, so archives it writes cannot be decrypted;
|
|
166
230
|
encryption stays read-verified only.
|
|
167
231
|
|
|
232
|
+
### Verified
|
|
233
|
+
- LZIP interop with the real `lzip` CLI (1.26): our members pass
|
|
234
|
+
`lzip -t`/`-dc` and the CLI's members decode through
|
|
235
|
+
`Formats::Lzip` (spec skips where the CLI is absent).
|
|
236
|
+
|
|
168
237
|
## [0.3.28] - 2026-08-26
|
|
169
238
|
|
|
170
239
|
### Added
|
data/README.adoc
CHANGED
|
@@ -115,13 +115,14 @@ Omnizip provides complete XZ container format (`.xz`) support with LZMA2 compres
|
|
|
115
115
|
----
|
|
116
116
|
require 'omnizip'
|
|
117
117
|
|
|
118
|
-
# Compress to XZ format
|
|
119
|
-
|
|
120
|
-
File.write('output.xz', compressed)
|
|
118
|
+
# Compress a file to XZ format
|
|
119
|
+
Omnizip::Formats::Xz.create_file('input.txt', level: 6)
|
|
121
120
|
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
# Compress in-memory data (returns the .xz bytes)
|
|
122
|
+
compressed = Omnizip::Formats::Xz.create("Hello, World!")
|
|
123
|
+
|
|
124
|
+
# Decompress (accepts a path or the .xz bytes)
|
|
125
|
+
decompressed = Omnizip::Formats::Xz.decompress(compressed)
|
|
125
126
|
|
|
126
127
|
# Or use the Reader API
|
|
127
128
|
reader = Omnizip::Formats::Xz::Reader.new('file.xz')
|
|
@@ -133,7 +134,7 @@ data = reader.read
|
|
|
133
134
|
[source,ruby]
|
|
134
135
|
----
|
|
135
136
|
# Configure checksum type
|
|
136
|
-
compressed = Omnizip::Formats::Xz.
|
|
137
|
+
compressed = Omnizip::Formats::Xz.create(data, check_type: :crc64)
|
|
137
138
|
# Options: :crc32 (default), :crc64, :sha256, :none
|
|
138
139
|
|
|
139
140
|
# Using Builder API for multi-part data
|
|
@@ -216,19 +217,19 @@ Omnizip provides complete 7-Zip container format (`.7z`) support with multiple c
|
|
|
216
217
|
require 'omnizip'
|
|
217
218
|
|
|
218
219
|
# Create 7z archive
|
|
219
|
-
Omnizip::Formats::SevenZip
|
|
220
|
+
Omnizip::Formats::SevenZip.create('archive.7z') do |sz|
|
|
220
221
|
sz.add_file('document.pdf')
|
|
221
222
|
sz.add_directory('photos/')
|
|
222
223
|
end
|
|
223
224
|
|
|
224
225
|
# Extract 7z archive
|
|
225
|
-
Omnizip::Formats::SevenZip
|
|
226
|
+
Omnizip::Formats::SevenZip.open('archive.7z') do |sz|
|
|
226
227
|
sz.extract_all('output/')
|
|
227
228
|
end
|
|
228
229
|
|
|
229
230
|
# List contents
|
|
230
|
-
Omnizip::Formats::SevenZip
|
|
231
|
-
sz.
|
|
231
|
+
Omnizip::Formats::SevenZip.open('archive.7z') do |sz|
|
|
232
|
+
sz.list_files.each do |entry|
|
|
232
233
|
puts "#{entry.name}: #{entry.size} bytes"
|
|
233
234
|
end
|
|
234
235
|
end
|
|
@@ -239,7 +240,7 @@ end
|
|
|
239
240
|
[source,ruby]
|
|
240
241
|
----
|
|
241
242
|
# With compression options
|
|
242
|
-
Omnizip::Formats::SevenZip
|
|
243
|
+
Omnizip::Formats::SevenZip.create('archive.7z',
|
|
243
244
|
algorithm: :lzma2,
|
|
244
245
|
level: 9,
|
|
245
246
|
solid: true
|
|
@@ -248,9 +249,9 @@ Omnizip::Formats::SevenZip::Writer.create('archive.7z',
|
|
|
248
249
|
end
|
|
249
250
|
|
|
250
251
|
# With password encryption
|
|
251
|
-
Omnizip::Formats::SevenZip
|
|
252
|
+
Omnizip::Formats::SevenZip.create('secure.7z',
|
|
252
253
|
password: 'secret123',
|
|
253
|
-
|
|
254
|
+
encrypt_headers: true
|
|
254
255
|
) do |sz|
|
|
255
256
|
sz.add_file('confidential.doc')
|
|
256
257
|
end
|
|
@@ -417,39 +418,23 @@ puts "#{info[:name]}-#{info[:version]}-#{info[:release]}"
|
|
|
417
418
|
----
|
|
418
419
|
require 'omnizip'
|
|
419
420
|
|
|
420
|
-
# Create RPM package with gzip
|
|
421
|
-
Omnizip::Formats::Rpm.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
rpm.release = '1'
|
|
425
|
-
rpm.arch = 'noarch'
|
|
426
|
-
rpm.summary = 'My awesome package'
|
|
427
|
-
rpm.description = 'A longer description of the package'
|
|
428
|
-
rpm.license = 'MIT'
|
|
429
|
-
rpm.vendor = 'My Company'
|
|
430
|
-
rpm.url = 'https://example.com/mypackage'
|
|
431
|
-
|
|
432
|
-
# Add files from filesystem
|
|
433
|
-
rpm.add_file('/usr/bin/myapp', 'path/to/myapp')
|
|
434
|
-
rpm.add_file('/etc/myapp.conf', 'path/to/config')
|
|
435
|
-
rpm.add_directory('/var/lib/myapp')
|
|
436
|
-
|
|
437
|
-
# Add dependencies
|
|
438
|
-
rpm.add_dependency('glibc', '>= 2.17')
|
|
439
|
-
rpm.add_provides('myapp')
|
|
440
|
-
end
|
|
421
|
+
# Create RPM package with gzip-compressed payload (default)
|
|
422
|
+
rpm = Omnizip::Formats::Rpm::Writer.new(
|
|
423
|
+
name: 'mypackage', version: '1.0', release: '1', arch: 'noarch'
|
|
424
|
+
)
|
|
441
425
|
|
|
442
|
-
#
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
rpm.arch = 'x86_64'
|
|
449
|
-
# ... add files
|
|
450
|
-
end
|
|
426
|
+
# Add payload entries (content is passed as a string)
|
|
427
|
+
rpm.add_file('/usr/bin/myapp', File.binread('path/to/myapp'), mode: 0o755)
|
|
428
|
+
rpm.add_file('/etc/myapp.conf', File.binread('path/to/config'))
|
|
429
|
+
rpm.add_directory('/var/lib/myapp')
|
|
430
|
+
|
|
431
|
+
rpm.write('mypackage-1.0-1.noarch.rpm')
|
|
451
432
|
|
|
452
433
|
# Supported compression types: :gzip (default), :bzip2, :xz, :zstd, :none
|
|
434
|
+
Omnizip::Formats::Rpm::Writer.new(
|
|
435
|
+
name: 'mypackage', version: '1.0', release: '1',
|
|
436
|
+
arch: 'x86_64', compression: :xz
|
|
437
|
+
)
|
|
453
438
|
----
|
|
454
439
|
|
|
455
440
|
**Architecture**:
|
|
@@ -544,8 +529,8 @@ See link:readme-docs/preprocessing-filters.adoc[Preprocessing Filters Guide] for
|
|
|
544
529
|
|
|
545
530
|
* **.7z** - Full read/write with solid compression, multi-volume support
|
|
546
531
|
* **ZIP** - Full read/write with ZIP64, WinZip AES encryption
|
|
547
|
-
* **RAR4** -
|
|
548
|
-
* **RAR5** -
|
|
532
|
+
* **RAR4** - Read support for real WinRAR archives (STORE natively; compressed methods via unrar), write with STORE verified against unrar, FASTEST through BEST for Omnizip round-trip (v0.3.0)
|
|
533
|
+
* **RAR5** - Read/write with STORE (unrar-verified), AES-256 encryption, multi-volume; compressed-method writing falls back to STORE (v0.3.0)
|
|
549
534
|
* **TAR** - Full read/write with POSIX extensions
|
|
550
535
|
* **ISO 9660** - Full read/write with Rock Ridge/Joliet
|
|
551
536
|
* **CPIO** - Full read/write (newc, CRC formats) with RPM payload support (v0.4.0)
|
|
@@ -679,16 +664,19 @@ File.open('input.txt', 'rb') do |input|
|
|
|
679
664
|
end
|
|
680
665
|
|
|
681
666
|
# .7z archive operations
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
667
|
+
Omnizip::Formats::SevenZip.create('archive.7z') do |sz|
|
|
668
|
+
sz.add_file('document.pdf')
|
|
669
|
+
end
|
|
685
670
|
----
|
|
686
671
|
|
|
687
672
|
==== RAR Archives
|
|
688
673
|
|
|
689
674
|
==== RAR4 Archives (v0.3.0)
|
|
690
675
|
|
|
691
|
-
|
|
676
|
+
RAR4 reading and writing against the on-disk format unrar accepts:
|
|
677
|
+
headers carry the low 16 bits of a CRC32, HEAD_SIZE counts the whole
|
|
678
|
+
block, and the dictionary mask 0xE0 marks directory entries. STORE
|
|
679
|
+
archives round-trip through official unrar byte-identically.
|
|
692
680
|
|
|
693
681
|
====== Reading RAR4 Archives
|
|
694
682
|
|
|
@@ -696,31 +684,29 @@ Omnizip v0.3.0 provides complete RAR4 archive support with full read capabilitie
|
|
|
696
684
|
----
|
|
697
685
|
require 'omnizip'
|
|
698
686
|
|
|
699
|
-
|
|
700
|
-
reader
|
|
701
|
-
File.open('archive.rar', 'rb') do |io|
|
|
702
|
-
entries = reader.read_archive(io)
|
|
687
|
+
reader = Omnizip::Formats::Rar::Reader.new('archive.rar')
|
|
688
|
+
reader.open
|
|
703
689
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
puts " Modified: #{entry.modified_time}"
|
|
709
|
-
puts " Directory: #{entry.is_directory}"
|
|
710
|
-
end
|
|
690
|
+
reader.list_files.each do |entry|
|
|
691
|
+
puts "#{entry.name}: #{entry.size} bytes (#{entry.compressed_size} compressed)"
|
|
692
|
+
puts " Directory: #{entry.is_dir}"
|
|
693
|
+
puts " Modified: #{entry.mtime}"
|
|
711
694
|
end
|
|
695
|
+
|
|
696
|
+
# STORE data decodes natively; WinRAR-compressed entries fall back to
|
|
697
|
+
# the unrar command after a CRC check rejects foreign streams
|
|
698
|
+
reader.extract_entry('test.txt', 'output/test.txt')
|
|
699
|
+
reader.extract_all('output/')
|
|
712
700
|
----
|
|
713
701
|
|
|
714
702
|
**RAR4 Reader Features:**
|
|
715
703
|
|
|
716
|
-
* ✅
|
|
717
|
-
* ✅
|
|
718
|
-
* ✅
|
|
719
|
-
* ✅
|
|
720
|
-
* ✅ Symlink detection and handling
|
|
704
|
+
* ✅ Block header parsing (MAIN/FILE/ENDARC), directories, empty files
|
|
705
|
+
* ✅ Native STORE decompression with stored-CRC32 verification
|
|
706
|
+
* ✅ unrar fallback for real WinRAR-compressed entries (byte-exact)
|
|
707
|
+
* ✅ DOS-style backslash name normalization to forward slashes
|
|
721
708
|
* ✅ Multi-volume archive detection
|
|
722
|
-
* ✅
|
|
723
|
-
* ✅ libarchive compatibility (52 test files verified)
|
|
709
|
+
* ✅ Parsing verified against libarchive WinRAR fixtures
|
|
724
710
|
|
|
725
711
|
====== Writing RAR4 Archives
|
|
726
712
|
|
|
@@ -728,141 +714,123 @@ end
|
|
|
728
714
|
----
|
|
729
715
|
require 'omnizip'
|
|
730
716
|
|
|
731
|
-
#
|
|
732
|
-
writer = Omnizip::Formats::Rar::Writer.new('archive.rar'
|
|
717
|
+
# STORE output is verified interoperable with unrar
|
|
718
|
+
writer = Omnizip::Formats::Rar::Writer.new('archive.rar',
|
|
719
|
+
compression: :store
|
|
720
|
+
)
|
|
733
721
|
writer.add_file('document.txt')
|
|
734
722
|
writer.add_file('image.png')
|
|
735
723
|
writer.add_directory('photos/')
|
|
736
724
|
writer.write
|
|
737
|
-
writer.close
|
|
738
|
-
|
|
739
|
-
# Or use block syntax
|
|
740
|
-
Omnizip::Formats::Rar::Writer.new('archive.rar') do |rar|
|
|
741
|
-
rar.add_file('document.txt')
|
|
742
|
-
rar.add_directory('photos/')
|
|
743
|
-
end
|
|
744
|
-
|
|
745
|
-
# Select compression method
|
|
746
|
-
writer = Omnizip::Formats::Rar::Writer.new('archive.rar',
|
|
747
|
-
compression_method: :normal # or :store, :fastest
|
|
748
|
-
)
|
|
749
|
-
writer.add_file('large_file.bin')
|
|
750
|
-
writer.write
|
|
751
725
|
----
|
|
752
726
|
|
|
727
|
+
Writer options: `compression:` (see below), `test_after_create: true`
|
|
728
|
+
(reads the archive back and verifies every file). RAR4 encryption is
|
|
729
|
+
not implemented — `password:` raises `NotImplementedError`. The
|
|
730
|
+
`solid:`/`recovery:`/`volume_size:` options are accepted but not
|
|
731
|
+
implemented; they are ignored with a warning and never advertised in
|
|
732
|
+
header flags.
|
|
733
|
+
|
|
753
734
|
====== RAR4 Compression Methods
|
|
754
735
|
|
|
755
736
|
[cols="2,2,2,4",options="header"]
|
|
756
737
|
|===
|
|
757
|
-
|Method |Speed |
|
|
758
|
-
|`:store` |Instant |
|
|
759
|
-
|`:fastest` |Very Fast
|
|
760
|
-
|`:normal` |Fast
|
|
761
|
-
|`:
|
|
738
|
+
|Method |Speed |unrar interop |Notes
|
|
739
|
+
|`:store` |Instant |✅ verified |Byte-identical round-trip incl. directories
|
|
740
|
+
|`:fastest` |Very Fast |❌ Omnizip-only |LZ77+Huffman, readable by Omnizip
|
|
741
|
+
|`:normal` (default) |Fast |❌ Omnizip-only |LZ77+Huffman, readable by Omnizip
|
|
742
|
+
|`:good` |Medium |❌ Omnizip-only |LZ77+Huffman, readable by Omnizip
|
|
743
|
+
|`:best` |Slow |❌ Omnizip-only |PPMd, readable by Omnizip
|
|
762
744
|
|===
|
|
763
745
|
|
|
746
|
+
Official RAR LZ/PPMd streams are proprietary; methods above `:store`
|
|
747
|
+
produce archives Omnizip can read back but unrar reports as checksum
|
|
748
|
+
errors. Use `:store` for interoperability.
|
|
749
|
+
|
|
764
750
|
===== RAR5 Archives (v0.3.0)
|
|
765
751
|
|
|
766
|
-
|
|
752
|
+
Read/write support for RAR5 archives with STORE compression,
|
|
753
|
+
AES-256 encryption, and multi-volume archives — all verified against
|
|
754
|
+
official unrar.
|
|
767
755
|
|
|
768
756
|
====== Reading RAR5 Archives
|
|
769
757
|
|
|
770
758
|
[source,ruby]
|
|
771
759
|
----
|
|
772
|
-
require 'omnizip
|
|
760
|
+
require 'omnizip'
|
|
773
761
|
|
|
774
|
-
# Read RAR5 archive
|
|
775
762
|
reader = Omnizip::Formats::Rar5::Reader.new
|
|
776
763
|
File.open('archive.rar', 'rb') do |io|
|
|
777
764
|
entries = reader.read_archive(io)
|
|
778
765
|
|
|
779
|
-
# List files with metadata
|
|
780
766
|
entries.each do |entry|
|
|
781
767
|
puts "#{entry.name}: #{entry.uncompressed_size} bytes"
|
|
782
|
-
puts "
|
|
783
|
-
puts "
|
|
784
|
-
puts " Modified: #{entry.modified_time}"
|
|
768
|
+
puts " CRC32: #{entry.crc32.to_s(16)}" if entry.crc32
|
|
769
|
+
puts " Modified: #{entry.modified_time}" if entry.modified_time
|
|
785
770
|
end
|
|
786
771
|
end
|
|
787
772
|
----
|
|
788
773
|
|
|
789
774
|
**RAR5 Reader Features:**
|
|
790
775
|
|
|
791
|
-
* ✅
|
|
792
|
-
* ✅
|
|
776
|
+
* ✅ STORE file data, header CRC32 verification
|
|
777
|
+
* ✅ 32-bit Unix mtime decoding (matches unrar; nil when absent)
|
|
793
778
|
* ✅ Unicode filenames (UTF-8)
|
|
794
|
-
* ✅ Symlink and hardlink support
|
|
795
779
|
* ✅ Multi-file archives
|
|
796
780
|
* ✅ VInt (variable-length integer) parsing
|
|
797
|
-
* ✅ Proper header tracking with bounds checking
|
|
798
781
|
* ✅ Graceful error handling for truncated/invalid files
|
|
799
|
-
* ✅ libarchive compatibility
|
|
782
|
+
* ✅ libarchive compatibility fixtures
|
|
800
783
|
|
|
801
784
|
====== Writing RAR5 Archives
|
|
802
785
|
|
|
803
786
|
[source,ruby]
|
|
804
787
|
----
|
|
805
|
-
require 'omnizip
|
|
788
|
+
require 'omnizip'
|
|
806
789
|
|
|
807
|
-
#
|
|
790
|
+
# STORE compression (default) — unrar-verified
|
|
808
791
|
writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar')
|
|
809
792
|
writer.add_file('document.txt')
|
|
810
793
|
writer.add_file('image.png')
|
|
811
794
|
writer.write
|
|
812
795
|
|
|
813
|
-
#
|
|
796
|
+
# AES-256 encryption
|
|
814
797
|
writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
|
|
815
|
-
|
|
816
|
-
level: 5 # 1=fastest, 3=normal, 5=best
|
|
798
|
+
password: 'secret'
|
|
817
799
|
)
|
|
818
800
|
writer.add_file('data.json')
|
|
819
801
|
writer.write
|
|
820
802
|
|
|
821
|
-
#
|
|
803
|
+
# Multi-volume archives split at 64 KB boundaries
|
|
822
804
|
writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
|
|
823
|
-
|
|
824
|
-
|
|
805
|
+
multi_volume: true,
|
|
806
|
+
volume_size: 65536
|
|
825
807
|
)
|
|
826
|
-
writer.add_file('
|
|
827
|
-
writer.add_file('
|
|
828
|
-
writer.write
|
|
808
|
+
writer.add_file('big1.bin')
|
|
809
|
+
writer.add_file('big2.bin')
|
|
810
|
+
writer.write # writes archive.part1.rar, archive.part2.rar, ...
|
|
829
811
|
----
|
|
830
812
|
|
|
831
813
|
====== RAR5 Compression Methods
|
|
832
814
|
|
|
833
|
-
[cols="2,2,
|
|
815
|
+
[cols="2,2,4",options="header"]
|
|
834
816
|
|===
|
|
835
|
-
|Method |
|
|
836
|
-
|`:store`
|
|
837
|
-
|`:lzma` |
|
|
838
|
-
|`:
|
|
839
|
-
|`:lzma` |3 |4 MB |✅ LZMA normal (default)
|
|
840
|
-
|`:lzma` |4 |8 MB |✅ LZMA good
|
|
841
|
-
|`:lzma` |5 |16 MB |✅ LZMA best
|
|
817
|
+
|Method |Interop |Notes
|
|
818
|
+
|`:store` (default) |✅ unrar-verified |Uncompressed passthrough
|
|
819
|
+
|`:lzss` / `:lzma` |falls back to STORE |Encoder is not official-RAR compatible; warns and stores
|
|
820
|
+
|`:auto` |STORE |Size-based selection
|
|
842
821
|
|===
|
|
843
822
|
|
|
844
|
-
====== RAR5
|
|
845
|
-
|
|
846
|
-
**Implemented (v0.3.0):**
|
|
847
|
-
|
|
848
|
-
* ✅ **STORE compression** - Uncompressed storage (method 0)
|
|
849
|
-
* ✅ **LZMA compression** - 5 compression levels (methods 1-5) - *SDK-compatible encoder*
|
|
850
|
-
* ✅ **Auto compression** - Smart selection based on file size
|
|
851
|
-
* ✅ **Multi-volume archives** - Split archives across multiple volumes
|
|
852
|
-
* ✅ **Solid compression** - 10-30% better compression for similar files
|
|
853
|
-
* ✅ **AES-256 encryption** - Password protection with PBKDF2-HMAC-SHA256
|
|
854
|
-
* ✅ **PAR2 recovery records** - Error correction with Reed-Solomon codes
|
|
855
|
-
* ✅ **Optional fields** - Modification time (mtime), CRC32 checksums
|
|
856
|
-
* ✅ **Pure Ruby** - Zero external dependencies
|
|
857
|
-
* ✅ **LZMA SDK compatibility** - Encoder produces byte-for-byte identical output to reference implementation
|
|
858
|
-
* ✅ **Full reader support** - All compression methods, solid archives, unicode, symlinks
|
|
823
|
+
====== RAR5 Optional Fields
|
|
859
824
|
|
|
860
|
-
|
|
825
|
+
The writer can embed metadata that readers use without decompressing:
|
|
861
826
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
827
|
+
[source,ruby]
|
|
828
|
+
----
|
|
829
|
+
writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
|
|
830
|
+
include_mtime: true, # 32-bit Unix mtime per file header
|
|
831
|
+
include_crc32: true # CRC32 per file header
|
|
832
|
+
)
|
|
833
|
+
----
|
|
866
834
|
|
|
867
835
|
====== RAR5 Optional Fields
|
|
868
836
|
|
|
@@ -92,10 +92,8 @@ GZIP supports streaming for large files:
|
|
|
92
92
|
----
|
|
93
93
|
# Stream compress large file
|
|
94
94
|
File.open('large.bin', 'rb') do |input|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
output.write(chunk)
|
|
98
|
-
end
|
|
95
|
+
File.open('large.bin.gz', 'wb') do |output|
|
|
96
|
+
Omnizip::Formats::Gzip.compress_stream(input, output)
|
|
99
97
|
end
|
|
100
98
|
end
|
|
101
99
|
----
|