omnizip 0.3.37 → 0.3.38
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 +76 -9
- 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/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.rb +5 -3
- data/lib/omnizip/formats/seven_zip/parser.rb +22 -7
- data/lib/omnizip/formats/seven_zip/writer.rb +78 -19
- data/lib/omnizip/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 67142de350c2f3dd29c473268977799ed088cedb0340932cff6db7bc23ac009e
|
|
4
|
+
data.tar.gz: c35fbc3353ca0c20f2ec4f86fa39fff67e6ed95822702a18c9b545d35406749c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7f6b4e88bb0a227f7cff47b06e979891e6d5a57288ae141bd15e57a07abe2cef3c915600242bb05406c4b7a13ef9fdd0ab5f11870e41b65f0a95077191741ceb
|
|
7
|
+
data.tar.gz: 3e8497c8eb222b4a139f965ec84f1672d8b12ae92e55986df95d8057c452bcace18a75c54a0464ca696776a33256d2e11ea53811c7d73e3ecb1c0eece06d04ff
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
- `.cpio` and `.iso` read routing: `extract_archive`/`list_archive`/
|
|
12
|
+
`read_from_archive`/`Archive.open` operate on CPIO archives and ISO
|
|
13
|
+
9660 images through new read-only handlers, mirroring the RAR
|
|
14
|
+
routing (creation keeps raising truthfully).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- 7z archives with explicit directory entries (or zero-byte
|
|
18
|
+
entries) were structurally invalid: the writer never emitted the
|
|
19
|
+
kEmptyStream/kEmptyFile properties, hardcoded 0x20 attributes
|
|
20
|
+
over each entry's real attributes, and its kSubStreamsInfo
|
|
21
|
+
digest/size arrays counted directory entries as substreams. 7-Zip
|
|
22
|
+
rejects such archives with "Headers Error" (and our own reader
|
|
23
|
+
crashed extracting directory entries). Verified against 7zz
|
|
24
|
+
ground truth (`-mhc=off` dumps): kEmptyStream/kEmptyFile carry
|
|
25
|
+
RAW bit vectors with no all-defined marker — the parser expected
|
|
26
|
+
that marker everywhere, so it now reads both layouts correctly
|
|
27
|
+
(which also fixes reading 7-Zip-created archives containing empty
|
|
28
|
+
files).
|
|
29
|
+
- RAR5 vint encoding was not the spec encoding: multi-byte values
|
|
30
|
+
(any size >= 128, extra-area sizes, dictionary sizes) were written
|
|
31
|
+
in a byte-swapped form no reader decodes — archives with more
|
|
32
|
+
than ~127 bytes of content were corrupt for unrar AND for our own
|
|
33
|
+
spec-conformant parser. All round-trips now verified against
|
|
34
|
+
unrar, including encrypted STORE, multi-file, and multi-volume
|
|
35
|
+
archives (volume flags were the previously documented "no
|
|
36
|
+
write-side reference" gap: Main archive flags 0x0001/0x0002 and
|
|
37
|
+
end-of-archive 0x0001 are written per the RAR 5.0 specification).
|
|
38
|
+
- RAR5 file headers: a "mystery vint" not in the spec shifted every
|
|
39
|
+
field; the compression method was written into the version bits;
|
|
40
|
+
the file-attribute value chmods extracted files unreadable. Now:
|
|
41
|
+
spec field order, method in bits 8-10 with dictionary bits
|
|
42
|
+
11-15, Unix attribute 0o100644.
|
|
43
|
+
- RAR5 encrypted writing now persists the salt/IV as the file
|
|
44
|
+
encryption extra record (spec type 0x01) — archives it wrote
|
|
45
|
+
before could not be decrypted by anything (unrar reported "All
|
|
46
|
+
OK" only because no CRC was stored).
|
|
47
|
+
- RAR5 `:lzma`/`:lzss` compression advertised method bits for a
|
|
48
|
+
stream that nothing can decode (the encoder is not
|
|
49
|
+
official-RAR-compatible; `unrar` extracts empty files silently).
|
|
50
|
+
`Lzss.available?` is now honest, so writers fall back to STORE
|
|
51
|
+
with a warning; solid mode falls back to independent STORE
|
|
52
|
+
entries.
|
|
53
|
+
- ISO writing: directory records pointed every file at sector 0
|
|
54
|
+
(allocated extents were never copied onto the tree nodes); the
|
|
55
|
+
volume descriptor declared 100 sectors regardless of actual
|
|
56
|
+
size; `add_directory` entries were not marked as directories;
|
|
57
|
+
Rock Ridge and Joliet were advertised by default without being
|
|
58
|
+
implemented (the cloned SVD made 7-Zip read ASCII names as
|
|
59
|
+
UCS-2); `Reader#extract_all` looked entries up by bare name
|
|
60
|
+
instead of full path; `Formats::Iso.list` returned the Reader
|
|
61
|
+
instead of entries. Images now extract byte-identically through
|
|
62
|
+
7zz, including nested directories.
|
|
63
|
+
- CLI: `omnizip <command> --help` failed with an arity error on
|
|
64
|
+
every command (Thor does not route `--help` on subcommands by
|
|
65
|
+
itself). A dispatch hook now prints per-command help.
|
|
66
|
+
- `Omnizip::Formats::Rar::Rar5::MainHeader/FileHeader` were
|
|
67
|
+
unreferenceable unless another file happened to load header.rb
|
|
68
|
+
first (missing autoload entries); three Rar5 autoloads pointed at
|
|
69
|
+
files that never existed.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
- Docs: the OLE, RPM, GZIP, RAR5 and API-overview guides now use
|
|
73
|
+
the real APIs (`Formats::Ole.list/read/info`, `Formats::Rpm.*`,
|
|
74
|
+
`Gzip.compress_stream`, `Formats::Zip::Reader#read`, correct
|
|
75
|
+
error class names); fictional methods (`open_stream`, `root`,
|
|
76
|
+
`extract_to`, `extract_files`, `extract_payload`,
|
|
77
|
+
`Rar::Rar5::Reader`, scriptlets/signature accessors) removed.
|
|
78
|
+
|
|
79
|
+
## [0.3.37] - 2026-08-28
|
|
80
|
+
|
|
10
81
|
### Added
|
|
11
82
|
- `Omnizip::Archive` — the block-style facade the guides have
|
|
12
83
|
documented since the docs were written (it never existed; ~50
|
|
@@ -143,15 +214,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
143
214
|
been pinned to :copy) and replaced the match-encoding TODO with a
|
|
144
215
|
wire-level round-trip verification of the distance-8/length
|
|
145
216
|
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
217
|
- The remaining lib/ TODO markers are resolved as documented design
|
|
156
218
|
decisions rather than dangling work: RAR5 volume extras and
|
|
157
219
|
EndHeader volume flags (no write-side reference exists;
|
|
@@ -165,6 +227,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
165
227
|
the salt/IV header, so archives it writes cannot be decrypted;
|
|
166
228
|
encryption stays read-verified only.
|
|
167
229
|
|
|
230
|
+
### Verified
|
|
231
|
+
- LZIP interop with the real `lzip` CLI (1.26): our members pass
|
|
232
|
+
`lzip -t`/`-dc` and the CLI's members decode through
|
|
233
|
+
`Formats::Lzip` (spec skips where the CLI is absent).
|
|
234
|
+
|
|
168
235
|
## [0.3.28] - 2026-08-26
|
|
169
236
|
|
|
170
237
|
### Added
|
|
@@ -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
|
----
|
|
@@ -8,37 +8,13 @@ grand_parent: Guides
|
|
|
8
8
|
[[ole-format]]
|
|
9
9
|
== Purpose
|
|
10
10
|
|
|
11
|
-
OLE (Object Linking and Embedding) Compound Document format is used
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- PPT (legacy PowerPoint presentations)
|
|
16
|
-
- THM (Microsoft Theme files)
|
|
17
|
-
- MSG (Outlook messages)
|
|
11
|
+
OLE (Object Linking and Embedding) Compound Document format is used
|
|
12
|
+
by Microsoft for storing structured data in a single file. Common OLE
|
|
13
|
+
files include MSI (Windows Installer packages), DOC (legacy Word),
|
|
14
|
+
XLS (legacy Excel), PPT (legacy PowerPoint) and THM (theme) files.
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
== Key Characteristics
|
|
22
|
-
|
|
23
|
-
[cols="1,3"]
|
|
24
|
-
|===
|
|
25
|
-
|Property |Value
|
|
26
|
-
|
|
27
|
-
|Max Size
|
|
28
|
-
|2GB (standard), 4GB (with 4KB sectors)
|
|
29
|
-
|
|
30
|
-
|Sector Sizes
|
|
31
|
-
|512 bytes (standard), 4KB (large)
|
|
32
|
-
|
|
33
|
-
|Streams
|
|
34
|
-
|Multiple streams and sub-storages
|
|
35
|
-
|
|
36
|
-
|Encryption
|
|
37
|
-
|Optional (document-specific)
|
|
38
|
-
|
|
39
|
-
|Best For
|
|
40
|
-
|Windows compound files, legacy Office documents, MSI installers
|
|
41
|
-
|===
|
|
16
|
+
The API is stream-oriented: open the compound document, list
|
|
17
|
+
streams and storages by path, and read the streams you need.
|
|
42
18
|
|
|
43
19
|
== Basic Usage
|
|
44
20
|
|
|
@@ -46,189 +22,104 @@ OLE uses a FAT-like file system within a single file, supporting up to 4GB of st
|
|
|
46
22
|
|
|
47
23
|
[source,ruby]
|
|
48
24
|
----
|
|
49
|
-
# Open OLE compound document
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
# Access root storage
|
|
53
|
-
root = ole.root
|
|
54
|
-
puts "Root storage: #{root.name}"
|
|
55
|
-
|
|
56
|
-
# List streams and storages
|
|
57
|
-
root.each do |entry|
|
|
58
|
-
puts "#{entry.name} (#{entry.size} bytes)" if entry.file?
|
|
59
|
-
puts "#{entry.name}/" if entry.directory?
|
|
25
|
+
# Open OLE compound document (block form closes automatically)
|
|
26
|
+
Omnizip::Formats::Ole.open('document.doc') do |ole|
|
|
27
|
+
puts ole.list.inspect
|
|
60
28
|
end
|
|
61
29
|
|
|
30
|
+
# Non-block form: close explicitly
|
|
31
|
+
ole = Omnizip::Formats::Ole.open('document.doc')
|
|
62
32
|
ole.close
|
|
63
33
|
----
|
|
64
34
|
|
|
35
|
+
=== List Entries
|
|
36
|
+
|
|
37
|
+
[source,ruby]
|
|
38
|
+
----
|
|
39
|
+
# Top-level streams and storages
|
|
40
|
+
Omnizip::Formats::Ole.list('document.doc')
|
|
41
|
+
|
|
42
|
+
# Inside a nested storage
|
|
43
|
+
Omnizip::Formats::Ole.list('document.doc', '/ObjectPool')
|
|
44
|
+
----
|
|
45
|
+
|
|
65
46
|
=== Read Stream Data
|
|
66
47
|
|
|
67
48
|
[source,ruby]
|
|
68
49
|
----
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
summary = ole.open_stream("\x01SummaryInformation")
|
|
74
|
-
if summary
|
|
75
|
-
data = summary.read
|
|
76
|
-
puts "Summary size: #{data.size} bytes"
|
|
77
|
-
end
|
|
50
|
+
# \x01SummaryInformation holds document metadata
|
|
51
|
+
summary = Omnizip::Formats::Ole.read('document.doc',
|
|
52
|
+
"\x01SummaryInformation")
|
|
53
|
+
puts "Summary size: #{summary.bytesize} bytes"
|
|
78
54
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
content = word_stream.read
|
|
83
|
-
puts "Document content size: #{content.size} bytes"
|
|
84
|
-
end
|
|
55
|
+
# WordDocument stream (main content)
|
|
56
|
+
content = Omnizip::Formats::Ole.read('document.doc', 'WordDocument')
|
|
57
|
+
puts "Document content size: #{content.bytesize} bytes"
|
|
85
58
|
|
|
86
|
-
|
|
59
|
+
# Write a stream to disk
|
|
60
|
+
File.binwrite('workbook.bin',
|
|
61
|
+
Omnizip::Formats::Ole.read('spreadsheet.xls', 'Workbook'))
|
|
87
62
|
----
|
|
88
63
|
|
|
89
|
-
===
|
|
64
|
+
=== Entry Introspection
|
|
90
65
|
|
|
91
66
|
[source,ruby]
|
|
92
67
|
----
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
ole.extract_to('/output/directory/')
|
|
96
|
-
end
|
|
68
|
+
info = Omnizip::Formats::Ole.info('document.doc', 'WordDocument')
|
|
69
|
+
puts info.inspect
|
|
97
70
|
|
|
98
|
-
#
|
|
99
|
-
Omnizip::Ole.
|
|
100
|
-
|
|
101
|
-
File.binwrite('workbook.bin', workbook.read)
|
|
102
|
-
end
|
|
71
|
+
Omnizip::Formats::Ole.exist?('document.doc', 'WordDocument') # => true
|
|
72
|
+
Omnizip::Formats::Ole.file?('document.doc', 'WordDocument') # => true
|
|
73
|
+
Omnizip::Formats::Ole.directory?('document.doc', 'ObjectPool') # => true
|
|
103
74
|
----
|
|
104
75
|
|
|
105
76
|
== MSI Package Inspection
|
|
106
77
|
|
|
107
|
-
MSI files are OLE compound documents
|
|
78
|
+
MSI files are OLE compound documents; the installer tables live in
|
|
79
|
+
storages and streams you can walk with +list+:
|
|
108
80
|
|
|
109
81
|
[source,ruby]
|
|
110
82
|
----
|
|
111
|
-
|
|
112
|
-
msi
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
puts "MSI Tables:"
|
|
117
|
-
tables.each { |t| puts " #{t.name}" }
|
|
118
|
-
|
|
119
|
-
# Access \x05SummaryInformation
|
|
120
|
-
summary = msi.open_stream("\x05SummaryInformation")
|
|
121
|
-
if summary
|
|
122
|
-
puts "Summary information found"
|
|
83
|
+
Omnizip::Formats::Ole.open('installer.msi') do |msi|
|
|
84
|
+
msi.list('/').each { |entry| puts entry }
|
|
85
|
+
summary = msi.read("\x05SummaryInformation") rescue nil
|
|
86
|
+
tables = msi.read('_Tables') rescue nil
|
|
87
|
+
puts "Tables stream size: #{tables&.bytesize}" if tables
|
|
123
88
|
end
|
|
124
|
-
|
|
125
|
-
# Access _Tables stream (list of all tables)
|
|
126
|
-
tables_stream = msi.open_stream("_Tables")
|
|
127
|
-
if tables_stream
|
|
128
|
-
puts "Tables stream size: #{tables_stream.size}"
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
msi.close
|
|
132
89
|
----
|
|
133
90
|
|
|
134
91
|
== Storage Structure
|
|
135
92
|
|
|
136
93
|
OLE files have a hierarchical structure like a file system:
|
|
137
94
|
|
|
138
|
-
[source]
|
|
139
95
|
----
|
|
140
96
|
Root Storage/
|
|
141
97
|
├── \x01SummaryInformation (document metadata)
|
|
142
|
-
├── \x01DocumentSummaryInformation
|
|
143
98
|
├── WordDocument (main content)
|
|
144
|
-
├── 1Table (data tables)
|
|
145
99
|
├── Data/
|
|
146
|
-
│
|
|
147
|
-
│ └── Mso0Table
|
|
100
|
+
│ └── Mso1Table
|
|
148
101
|
└── ObjectPool/
|
|
149
|
-
└──
|
|
150
|
-
└── Ole10Native
|
|
151
|
-
----
|
|
152
|
-
|
|
153
|
-
[source,ruby]
|
|
154
|
-
----
|
|
155
|
-
# Navigate storage hierarchy
|
|
156
|
-
ole = Omnizip::Ole.open('document.doc')
|
|
157
|
-
|
|
158
|
-
# Access nested storage
|
|
159
|
-
pool = ole.root.find("ObjectPool")
|
|
160
|
-
if pool
|
|
161
|
-
pool.each do |entry|
|
|
162
|
-
puts "ObjectPool/#{entry.name}"
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
ole.close
|
|
167
|
-
----
|
|
168
|
-
|
|
169
|
-
== Summary Information
|
|
170
|
-
|
|
171
|
-
OLE documents contain standard metadata streams:
|
|
172
|
-
|
|
173
|
-
[source,ruby]
|
|
102
|
+
└── Ole10Native
|
|
174
103
|
----
|
|
175
|
-
# Read summary information
|
|
176
|
-
ole = Omnizip::Ole.open('document.doc')
|
|
177
|
-
|
|
178
|
-
# Access summary stream
|
|
179
|
-
summary_stream = ole.open_stream("\x01SummaryInformation")
|
|
180
|
-
if summary_stream
|
|
181
|
-
# Summary information is in a specific binary format
|
|
182
|
-
# Omnizip provides helpers to decode it
|
|
183
|
-
summary = ole.summary_information
|
|
184
|
-
if summary
|
|
185
|
-
puts "Title: #{summary.title}"
|
|
186
|
-
puts "Author: #{summary.author}"
|
|
187
|
-
puts "Subject: #{summary.subject}"
|
|
188
|
-
puts "Keywords: #{summary.keywords}"
|
|
189
|
-
puts "Created: #{summary.create_time}"
|
|
190
|
-
puts "Modified: #{summary.last_save_time}"
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
104
|
|
|
194
|
-
|
|
195
|
-
----
|
|
105
|
+
Stream paths use `/` separators (e.g. +"ObjectPool/Ole10Native"+).
|
|
196
106
|
|
|
197
107
|
== File Format Detection
|
|
198
108
|
|
|
199
|
-
OLE files
|
|
109
|
+
OLE files carry the signature +D0 CF 11 E0 A1 B1 1A E1+ in the first
|
|
110
|
+
8 bytes:
|
|
200
111
|
|
|
201
112
|
[source,ruby]
|
|
202
113
|
----
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
puts "This is an OLE compound document"
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Get OLE file type
|
|
212
|
-
ole = Omnizip::Ole.open('document.doc')
|
|
213
|
-
case ole.file_type
|
|
214
|
-
when :word_document
|
|
215
|
-
puts "Microsoft Word document"
|
|
216
|
-
when :excel_spreadsheet
|
|
217
|
-
puts "Microsoft Excel spreadsheet"
|
|
218
|
-
when :powerpoint
|
|
219
|
-
puts "Microsoft PowerPoint presentation"
|
|
220
|
-
when :msi_package
|
|
221
|
-
puts "Windows Installer package"
|
|
222
|
-
else
|
|
223
|
-
puts "Unknown OLE type: #{ole.file_type}"
|
|
114
|
+
File.open('document.doc', "rb") do |f|
|
|
115
|
+
magic = f.read(8)
|
|
116
|
+
ole = magic == "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1".b
|
|
117
|
+
puts(ole ? "OLE compound document" : "not OLE")
|
|
224
118
|
end
|
|
225
|
-
ole.close
|
|
226
119
|
----
|
|
227
120
|
|
|
228
121
|
== Supported File Types
|
|
229
122
|
|
|
230
|
-
Omnizip supports reading the following OLE-based formats:
|
|
231
|
-
|
|
232
123
|
[cols="2,1,1"]
|
|
233
124
|
|===
|
|
234
125
|
|Format |Extension |Read Support
|
|
@@ -249,67 +140,11 @@ Omnizip supports reading the following OLE-based formats:
|
|
|
249
140
|
|.msi
|
|
250
141
|
|Yes
|
|
251
142
|
|
|
252
|
-
|Microsoft Theme
|
|
253
|
-
|.thm
|
|
254
|
-
|Yes
|
|
255
|
-
|
|
256
|
-
|Outlook Message
|
|
257
|
-
|.msg
|
|
258
|
-
|Yes
|
|
259
|
-
|
|
260
143
|
|Generic OLE
|
|
261
144
|
|.ole
|
|
262
145
|
|Yes
|
|
263
146
|
|===
|
|
264
147
|
|
|
265
|
-
== Low-Level Access
|
|
266
|
-
|
|
267
|
-
For advanced use cases, you can access the OLE internals directly:
|
|
268
|
-
|
|
269
|
-
[source,ruby]
|
|
270
|
-
----
|
|
271
|
-
# Access BAT (Block Allocation Table)
|
|
272
|
-
ole = Omnizip::Ole.open('document.doc')
|
|
273
|
-
|
|
274
|
-
# Get sector size
|
|
275
|
-
puts "Sector size: #{ole.sector_size}"
|
|
276
|
-
|
|
277
|
-
# Access BAT entries
|
|
278
|
-
bat = ole.bat
|
|
279
|
-
puts "BAT entries: #{bat.size}"
|
|
280
|
-
|
|
281
|
-
# Access SBAT (Small Block Allocation Table)
|
|
282
|
-
sbat = ole.sbat
|
|
283
|
-
puts "SBAT entries: #{sbat.size}" if sbat
|
|
284
|
-
|
|
285
|
-
# Access directory entries
|
|
286
|
-
ole.root.each do |entry|
|
|
287
|
-
puts "#{entry.name}: starting_sector=#{entry.starting_sector}, size=#{entry.size}"
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
ole.close
|
|
291
|
-
----
|
|
292
|
-
|
|
293
|
-
== Performance Considerations
|
|
294
|
-
|
|
295
|
-
OLE files can be large. For efficient reading:
|
|
296
|
-
|
|
297
|
-
[source,ruby]
|
|
298
|
-
----
|
|
299
|
-
# Stream read large OLE files efficiently
|
|
300
|
-
Omnizip::Ole.open('large.msi') do |ole|
|
|
301
|
-
# Only read streams you need
|
|
302
|
-
summary = ole.open_stream("\x05SummaryInformation")
|
|
303
|
-
if summary
|
|
304
|
-
# Read in chunks for large streams
|
|
305
|
-
chunk_size = 65536
|
|
306
|
-
while chunk = summary.read(chunk_size)
|
|
307
|
-
process(chunk)
|
|
308
|
-
end
|
|
309
|
-
end
|
|
310
|
-
end
|
|
311
|
-
----
|
|
312
|
-
|
|
313
148
|
== See Also
|
|
314
149
|
|
|
315
150
|
* link:seven-zip-format.html[7z Format] - Can contain OLE files
|
|
@@ -493,24 +493,24 @@ volumes.each { |v| puts " - #{File.basename(v)}" }
|
|
|
493
493
|
|
|
494
494
|
[source,ruby]
|
|
495
495
|
----
|
|
496
|
-
# Convert ZIP to RAR5
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
496
|
+
# Convert ZIP to RAR5 (extract first: the RAR5 writer adds files
|
|
497
|
+
# from disk)
|
|
498
|
+
def zip_to_rar5(zip_file, rar_file, workdir)
|
|
499
|
+
require "fileutils"
|
|
500
|
+
|
|
501
|
+
# Reader#read parses the central directory and returns self
|
|
502
|
+
zip = Omnizip::Formats::Zip::Reader.new(zip_file).read
|
|
503
|
+
zip.extract_all(workdir)
|
|
504
|
+
|
|
505
|
+
writer = Omnizip::Formats::Rar::Rar5::Writer.new(rar_file,
|
|
506
|
+
compression: :store)
|
|
507
|
+
Dir.glob(File.join(workdir, "**", "*")).each do |path|
|
|
508
|
+
writer.add_file(path, path.delete_prefix("#{workdir}/")) if File.file?(path)
|
|
508
509
|
end
|
|
509
|
-
|
|
510
|
-
zip.close
|
|
510
|
+
writer.write
|
|
511
511
|
end
|
|
512
512
|
|
|
513
|
-
zip_to_rar5('archive.zip', 'archive.rar')
|
|
513
|
+
zip_to_rar5('archive.zip', 'archive.rar', 'workdir')
|
|
514
514
|
----
|
|
515
515
|
|
|
516
516
|
=== See Also
|