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
|
@@ -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
|
|
@@ -8,242 +8,101 @@ grand_parent: Guides
|
|
|
8
8
|
[[rpm-format]]
|
|
9
9
|
== Purpose
|
|
10
10
|
|
|
11
|
-
RPM
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
[cols="1,3"]
|
|
16
|
-
|===
|
|
17
|
-
|Property |Value
|
|
18
|
-
|
|
19
|
-
|Compression
|
|
20
|
-
|gzip, bzip2, xz, or zstd
|
|
21
|
-
|
|
22
|
-
|Encryption
|
|
23
|
-
|GPG signature verification
|
|
24
|
-
|
|
25
|
-
|Archive Support
|
|
26
|
-
|Single package with CPIO payload
|
|
27
|
-
|
|
28
|
-
|Large Files
|
|
29
|
-
|Limited by CPIO and compression
|
|
30
|
-
|
|
31
|
-
|Best For
|
|
32
|
-
|Linux software distribution, RPM-based systems
|
|
33
|
-
|===
|
|
11
|
+
RPM is the package format of Red Hat-based Linux distributions. An
|
|
12
|
+
RPM file is a lead, signature, metadata header and a payload — a
|
|
13
|
+
CPIO archive compressed with gzip/bzip2/xz/zstd.
|
|
34
14
|
|
|
35
15
|
== Basic Usage
|
|
36
16
|
|
|
37
|
-
===
|
|
17
|
+
=== Open a Package
|
|
38
18
|
|
|
39
19
|
[source,ruby]
|
|
40
20
|
----
|
|
41
|
-
|
|
42
|
-
rpm
|
|
43
|
-
|
|
44
|
-
puts "Name: #{rpm.name}"
|
|
45
|
-
puts "Version: #{rpm.version}"
|
|
46
|
-
puts "Release: #{rpm.release}"
|
|
47
|
-
puts "Architecture: #{rpm.arch}"
|
|
48
|
-
puts "Summary: #{rpm.summary}"
|
|
49
|
-
puts "Description: #{rpm.description}"
|
|
50
|
-
|
|
51
|
-
# List package contents
|
|
52
|
-
rpm.each_entry do |entry|
|
|
53
|
-
puts "#{entry.path} (#{entry.size} bytes)"
|
|
21
|
+
Omnizip::Formats::Rpm.open('package.rpm') do |rpm|
|
|
22
|
+
puts rpm.files.size
|
|
54
23
|
end
|
|
55
24
|
|
|
25
|
+
rpm = Omnizip::Formats::Rpm.open('package.rpm')
|
|
56
26
|
rpm.close
|
|
57
27
|
----
|
|
58
28
|
|
|
59
|
-
===
|
|
29
|
+
=== Package Metadata
|
|
60
30
|
|
|
61
31
|
[source,ruby]
|
|
62
32
|
----
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
33
|
+
info = Omnizip::Formats::Rpm.info('package.rpm')
|
|
34
|
+
puts info[:name] # => "app"
|
|
35
|
+
puts info[:version]
|
|
36
|
+
puts info[:release]
|
|
37
|
+
puts info[:arch]
|
|
38
|
+
puts info[:summary]
|
|
39
|
+
puts info[:license]
|
|
40
|
+
puts info[:vendor]
|
|
41
|
+
puts info[:build_time]
|
|
42
|
+
puts info[:file_count]
|
|
43
|
+
|
|
44
|
+
# Same details on the reader
|
|
45
|
+
Omnizip::Formats::Rpm.open('package.rpm') do |rpm|
|
|
46
|
+
puts rpm.name
|
|
47
|
+
puts rpm.version
|
|
48
|
+
puts rpm.license
|
|
69
49
|
end
|
|
70
50
|
----
|
|
71
51
|
|
|
72
|
-
===
|
|
52
|
+
=== List and Extract Files
|
|
73
53
|
|
|
74
54
|
[source,ruby]
|
|
75
55
|
----
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
56
|
+
# File paths inside the payload
|
|
57
|
+
Omnizip::Formats::Rpm.list('package.rpm')
|
|
58
|
+
|
|
59
|
+
# Extract the payload (CPIO contents)
|
|
60
|
+
Omnizip::Formats::Rpm.extract('package.rpm', '/output/directory/')
|
|
61
|
+
|
|
62
|
+
# Or per-reader
|
|
63
|
+
Omnizip::Formats::Rpm.open('package.rpm') do |rpm|
|
|
64
|
+
rpm.extract('/output/directory/')
|
|
81
65
|
end
|
|
82
66
|
----
|
|
83
67
|
|
|
84
68
|
== RPM Structure
|
|
85
69
|
|
|
86
|
-
RPM packages consist of:
|
|
87
|
-
|
|
88
70
|
. **Lead** - 96 bytes identifying the package
|
|
89
|
-
. **Signature** -
|
|
90
|
-
. **Header** -
|
|
91
|
-
. **Payload** - CPIO archive
|
|
71
|
+
. **Signature** - cryptographic signatures
|
|
72
|
+
. **Header** - metadata (name, version, dependencies)
|
|
73
|
+
. **Payload** - CPIO archive
|
|
92
74
|
|
|
93
75
|
[source,ruby]
|
|
94
76
|
----
|
|
95
|
-
|
|
96
|
-
rpm = Omnizip::Rpm.open('package.rpm')
|
|
97
|
-
|
|
98
|
-
# Access lead
|
|
77
|
+
rpm = Omnizip::Formats::Rpm.open('package.rpm')
|
|
99
78
|
puts rpm.lead.inspect
|
|
100
|
-
|
|
101
|
-
# Access signature
|
|
102
79
|
puts rpm.signature.inspect
|
|
103
|
-
|
|
104
|
-
# Access header (metadata)
|
|
105
80
|
puts rpm.header.inspect
|
|
106
|
-
|
|
107
|
-
# Access payload directly (compressed CPIO)
|
|
108
|
-
puts rpm.payload.inspect
|
|
109
81
|
----
|
|
110
82
|
|
|
111
|
-
== Payload
|
|
112
|
-
|
|
113
|
-
The RPM payload is a CPIO archive that can be extracted:
|
|
83
|
+
== Payload
|
|
114
84
|
|
|
115
85
|
[source,ruby]
|
|
116
86
|
----
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
rpm.extract_payload('/output/directory/')
|
|
120
|
-
end
|
|
87
|
+
Omnizip::Formats::Rpm.open('package.rpm') do |rpm|
|
|
88
|
+
puts "Compression: #{rpm.payload_compressor}"
|
|
121
89
|
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
rpm.each_payload_entry do |entry|
|
|
125
|
-
puts "Entry: #{entry.path}"
|
|
126
|
-
puts "Content: #{entry.read[0, 100]}..."
|
|
127
|
-
end
|
|
90
|
+
# Compressed CPIO bytes; decompress with the matching format
|
|
91
|
+
payload = rpm.payload
|
|
128
92
|
end
|
|
129
93
|
----
|
|
130
94
|
|
|
131
|
-
== Compression Types
|
|
132
|
-
|
|
133
|
-
RPM supports four payload compression formats:
|
|
134
|
-
|
|
135
|
-
[cols="2,1,1"]
|
|
136
|
-
|===
|
|
137
|
-
|Format |Extension |Compression Ratio
|
|
138
|
-
|
|
139
|
-
|gzip
|
|
140
|
-
|.gz
|
|
141
|
-
|Good
|
|
142
|
-
|
|
143
|
-
|bzip2
|
|
144
|
-
|.bz2
|
|
145
|
-
|Better
|
|
146
|
-
|
|
147
|
-
|xz
|
|
148
|
-
|.xz
|
|
149
|
-
|Best
|
|
150
|
-
|
|
151
|
-
|zstd
|
|
152
|
-
|.zst
|
|
153
|
-
|Best (modern)
|
|
154
|
-
|===
|
|
155
|
-
|
|
156
|
-
[source,ruby]
|
|
157
|
-
----
|
|
158
|
-
# Check compression type
|
|
159
|
-
rpm = Omnizip::Rpm.open('package.rpm')
|
|
160
|
-
puts "Compression: #{rpm.compression_type}"
|
|
161
|
-
|
|
162
|
-
# Handle different compression types
|
|
163
|
-
case rpm.compression_type
|
|
164
|
-
when :gzip
|
|
165
|
-
puts "Using gzip decompression"
|
|
166
|
-
when :bzip2
|
|
167
|
-
puts "Using bzip2 decompression"
|
|
168
|
-
when :xz
|
|
169
|
-
puts "Using xz decompression"
|
|
170
|
-
when :zstd
|
|
171
|
-
puts "Using zstd decompression"
|
|
172
|
-
end
|
|
173
|
-
----
|
|
174
|
-
|
|
175
|
-
== Scriptlets
|
|
176
|
-
|
|
177
|
-
RPM packages can contain pre/post install scripts:
|
|
178
|
-
|
|
179
|
-
[source,ruby]
|
|
180
|
-
----
|
|
181
|
-
# Access scriptlets
|
|
182
|
-
rpm = Omnizip::Rpm.open('package.rpm')
|
|
183
|
-
|
|
184
|
-
puts "Pre-install script: #{rpm.pre_install_script}"
|
|
185
|
-
puts "Post-install script: #{rpm.post_install_script}"
|
|
186
|
-
puts "Pre-uninstall script: #{rpm.pre_uninstall_script}"
|
|
187
|
-
puts "Post-uninstall script: #{rpm.post_uninstall_script}"
|
|
188
|
-
----
|
|
189
|
-
|
|
190
95
|
== Dependencies
|
|
191
96
|
|
|
192
|
-
RPM packages declare dependencies:
|
|
193
|
-
|
|
194
|
-
[source,ruby]
|
|
195
|
-
----
|
|
196
|
-
# Access dependencies
|
|
197
|
-
rpm = Omnizip::Rpm.open('package.rpm')
|
|
198
|
-
|
|
199
|
-
# Required packages
|
|
200
|
-
puts "Requires:"
|
|
201
|
-
rpm.requires.each do |dep|
|
|
202
|
-
puts " #{dep.name} #{dep.version}"
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# Provided capabilities
|
|
206
|
-
puts "Provides:"
|
|
207
|
-
rpm.provides.each do |cap|
|
|
208
|
-
puts " #{cap.name}"
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Required capabilities
|
|
212
|
-
puts "Requires:"
|
|
213
|
-
rpm.requires.each do |req|
|
|
214
|
-
puts " #{req.name} #{req.version}"
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
# Conflicts
|
|
218
|
-
puts "Conflicts:"
|
|
219
|
-
rpm.conflicts.each do |conf|
|
|
220
|
-
puts " #{conf.name} #{conf.version}"
|
|
221
|
-
end
|
|
222
|
-
----
|
|
223
|
-
|
|
224
|
-
== File Information
|
|
225
|
-
|
|
226
|
-
RPM tracks detailed file information:
|
|
227
|
-
|
|
228
97
|
[source,ruby]
|
|
229
98
|
----
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
rpm.
|
|
233
|
-
|
|
234
|
-
puts "Size: #{file.size}"
|
|
235
|
-
puts "Mode: #{file.mode.to_s(8)}"
|
|
236
|
-
puts "Owner: #{file.owner}"
|
|
237
|
-
puts "Group: #{file.group}"
|
|
238
|
-
puts "MD5: #{file.md5}" if file.md5
|
|
239
|
-
puts "Is config: #{file.config?}"
|
|
240
|
-
puts "Is doc: #{file.doc?}"
|
|
241
|
-
end
|
|
99
|
+
Omnizip::Formats::Rpm.open('package.rpm') do |rpm|
|
|
100
|
+
rpm.requires.each { |name, op, version| puts "#{name} #{op} #{version}" }
|
|
101
|
+
rpm.provides.each { |name, op, version| puts "#{name} #{op} #{version}" }
|
|
102
|
+
rpm.conflicts.each { |name, op, version| puts "#{name} #{op} #{version}" }
|
|
242
103
|
end
|
|
243
104
|
----
|
|
244
105
|
|
|
245
106
|
== See Also
|
|
246
107
|
|
|
247
|
-
* link:
|
|
248
|
-
* link:xz-format.html[XZ Format] - XZ compression used in some RPMs
|
|
249
|
-
* link:cpio-format.html[CPIO Format] - Underlying payload format
|
|
108
|
+
* link:gzip-format.html[GZIP Format] - Common payload compression
|