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
|
@@ -42,9 +42,11 @@ module Omnizip
|
|
|
42
42
|
#
|
|
43
43
|
# @return [Boolean] true if implemented
|
|
44
44
|
def available?
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
|
|
45
|
+
# Decoder is implemented; the encoder is NOT compatible
|
|
46
|
+
# with official RAR tools (unrar "tests" its output as
|
|
47
|
+
# OK while extracting empty files). Callers fall back
|
|
48
|
+
# to STORE until a RAR-compatible encoder lands.
|
|
49
|
+
false
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
# Compress data using RAR5 LZSS
|
|
@@ -89,9 +89,18 @@ header_data: "")
|
|
|
89
89
|
|
|
90
90
|
# Main archive header
|
|
91
91
|
class MainHeader < Header
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
# Archive flags: 0x0001 volume, 0x0002 volume number
|
|
93
|
+
# present, 0x0004 solid, 0x0008 recovery, 0x0010 locked
|
|
94
|
+
ARCHIVE_FLAG_VOLUME = 0x0001
|
|
95
|
+
ARCHIVE_FLAG_VOLUME_NUMBER = 0x0002
|
|
96
|
+
|
|
97
|
+
def initialize(archive_flags: 0, volume_number: nil)
|
|
98
|
+
data = VINT.encode(archive_flags)
|
|
99
|
+
if archive_flags.anybits?(ARCHIVE_FLAG_VOLUME_NUMBER)
|
|
100
|
+
data.concat(VINT.encode(volume_number.to_i))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
super(HEADER_TYPE_MAIN, header_data: data.pack("C*"))
|
|
95
104
|
end
|
|
96
105
|
end
|
|
97
106
|
|
|
@@ -102,69 +111,73 @@ header_data: "")
|
|
|
102
111
|
FILE_HAS_MTIME = 0x0002
|
|
103
112
|
FILE_HAS_CRC32 = 0x0004
|
|
104
113
|
|
|
114
|
+
# Compression information field layout (RAR 5.0 spec):
|
|
115
|
+
# bits 0-5 algorithm version (0), bit 7 solid,
|
|
116
|
+
# bits 8-10 method (0=store, 1-5=LZMA), bits 11-15
|
|
117
|
+
# dictionary size (128 KB * 2^N).
|
|
118
|
+
DICT_BASE = 128 * 1024
|
|
119
|
+
|
|
105
120
|
def initialize(filename:, file_size:, compressed_size:,
|
|
106
|
-
compression_method: 0,
|
|
121
|
+
compression_method: 0, dict_size: 0,
|
|
122
|
+
flags: 0, mtime: nil, crc32: nil, extra_area: nil)
|
|
107
123
|
# Build file flags based on what's provided
|
|
108
124
|
file_flags = 0
|
|
109
125
|
file_flags |= FILE_HAS_MTIME if mtime
|
|
110
126
|
file_flags |= FILE_HAS_CRC32 if crc32
|
|
111
127
|
|
|
112
128
|
# Build header data with file information
|
|
113
|
-
data = build_file_data(filename, file_size,
|
|
114
|
-
|
|
129
|
+
data = build_file_data(filename, file_size,
|
|
130
|
+
compression_method, dict_size,
|
|
131
|
+
file_flags, mtime, crc32)
|
|
115
132
|
super(HEADER_TYPE_FILE, flags: flags, data_area_size: compressed_size, header_data: data, extra_area: extra_area)
|
|
116
133
|
end
|
|
117
134
|
|
|
118
135
|
private
|
|
119
136
|
|
|
120
|
-
def build_file_data(filename, file_size,
|
|
121
|
-
|
|
137
|
+
def build_file_data(filename, file_size, compression_method,
|
|
138
|
+
dict_size, file_flags, mtime, crc32)
|
|
122
139
|
data = []
|
|
123
140
|
|
|
124
141
|
# File flags (VINT)
|
|
125
142
|
data.concat(VINT.encode(file_flags))
|
|
126
143
|
|
|
127
|
-
#
|
|
144
|
+
# Unpacked size (VINT)
|
|
128
145
|
data.concat(VINT.encode(file_size))
|
|
129
146
|
|
|
130
|
-
# Attributes (VINT) - ALWAYS present in RAR5
|
|
131
|
-
#
|
|
132
|
-
data.concat(VINT.encode(
|
|
133
|
-
|
|
134
|
-
# Mystery VINT with value 0x02 - observed in official RAR
|
|
135
|
-
# This appears after attributes in official archives - ALWAYS present
|
|
136
|
-
data.concat(VINT.encode(0x02))
|
|
147
|
+
# Attributes (VINT) - ALWAYS present in RAR5.
|
|
148
|
+
# Unix host: standard regular-file mode 0o100644.
|
|
149
|
+
data.concat(VINT.encode(0o100644))
|
|
137
150
|
|
|
138
151
|
# mtime (optional) - only if FILE_HAS_MTIME flag is set
|
|
139
|
-
# RAR5 stores mtime as Unix timestamp (seconds since epoch)
|
|
152
|
+
# RAR5 stores mtime as Unix timestamp (seconds since epoch)
|
|
140
153
|
# Format: 4 bytes little-endian (NOT a VINT)
|
|
141
154
|
if file_flags.anybits?(FILE_HAS_MTIME) && mtime
|
|
142
|
-
|
|
143
|
-
unix_time = mtime.to_i
|
|
144
|
-
# Pack as 32-bit unsigned little-endian
|
|
145
|
-
data.concat([unix_time].pack("V").bytes)
|
|
155
|
+
data.concat([mtime.to_i].pack("V").bytes)
|
|
146
156
|
end
|
|
147
157
|
|
|
148
158
|
# Data CRC32 (optional) - only if FILE_HAS_CRC32 flag is set
|
|
149
159
|
# Format: 4 bytes little-endian (NOT a VINT)
|
|
150
160
|
if file_flags.anybits?(FILE_HAS_CRC32) && crc32
|
|
151
|
-
# Pack as 32-bit unsigned little-endian
|
|
152
161
|
data.concat([crc32].pack("V").bytes)
|
|
153
162
|
end
|
|
154
163
|
|
|
155
|
-
# Compression
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
|
|
164
|
+
# Compression information (VINT): version 0 in bits 0-5,
|
|
165
|
+
# method in bits 8-10, dictionary size in bits 11-15
|
|
166
|
+
# (128 KB * 2^N).
|
|
167
|
+
dict_bits = if dict_size.positive?
|
|
168
|
+
Math.log2(dict_size / DICT_BASE).round.clamp(0, 15)
|
|
169
|
+
else
|
|
170
|
+
0
|
|
171
|
+
end
|
|
172
|
+
compression_info = (compression_method << 8) | (dict_bits << 11)
|
|
173
|
+
data.concat(VINT.encode(compression_info))
|
|
159
174
|
|
|
160
175
|
# Host OS (VINT) - 1 = Unix
|
|
161
|
-
data.concat(VINT.encode(1))
|
|
176
|
+
data.concat(VINT.encode(1))
|
|
162
177
|
|
|
163
|
-
# Name length (VINT)
|
|
178
|
+
# Name length (VINT) and name
|
|
164
179
|
name_bytes = filename.encode("UTF-8").bytes
|
|
165
180
|
data.concat(VINT.encode(name_bytes.size))
|
|
166
|
-
|
|
167
|
-
# Name
|
|
168
181
|
data.concat(name_bytes)
|
|
169
182
|
|
|
170
183
|
data.pack("C*")
|
|
@@ -173,9 +186,13 @@ file_flags, compression_method, mtime, crc32)
|
|
|
173
186
|
|
|
174
187
|
# End of archive header
|
|
175
188
|
class EndHeader < Header
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
189
|
+
# End of archive flags: 0x0001 volume and not the last
|
|
190
|
+
# volume in the set
|
|
191
|
+
END_FLAG_VOLUME_NEXT = 0x0001
|
|
192
|
+
|
|
193
|
+
def initialize(end_flags: 0)
|
|
194
|
+
super(HEADER_TYPE_END,
|
|
195
|
+
header_data: VINT.encode(end_flags).pack("C*"))
|
|
179
196
|
end
|
|
180
197
|
end
|
|
181
198
|
end
|
|
@@ -18,11 +18,10 @@ module Omnizip
|
|
|
18
18
|
# writer.write_end_header
|
|
19
19
|
# writer.close
|
|
20
20
|
class VolumeWriter
|
|
21
|
-
# Main
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
VOLUME_NUMBER_FLAG = 0x0008 # Bit 3: Volume number present in extra area
|
|
21
|
+
# Main ARCHIVE flags (spec): 0x0001 volume,
|
|
22
|
+
# 0x0002 volume number field present
|
|
23
|
+
VOLUME_ARCHIVE_FLAG = MainHeader::ARCHIVE_FLAG_VOLUME
|
|
24
|
+
VOLUME_NUMBER_FLAG = MainHeader::ARCHIVE_FLAG_VOLUME_NUMBER
|
|
26
25
|
|
|
27
26
|
# End header flags
|
|
28
27
|
VOLUME_END_FLAG = 0x0001 # Not the last volume (more volumes follow)
|
|
@@ -95,26 +94,18 @@ module Omnizip
|
|
|
95
94
|
def write_main_header
|
|
96
95
|
raise "Volume not open" unless @io
|
|
97
96
|
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
nil
|
|
97
|
+
# Archive flags: every volume declares 0x0001; volumes
|
|
98
|
+
# after the first also carry the volume number field
|
|
99
|
+
# (spec: number is N-1, absent on the first volume).
|
|
100
|
+
archive_flags = VOLUME_ARCHIVE_FLAG
|
|
101
|
+
volume_number = nil
|
|
103
102
|
if @volume_number > 1
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
VINT.encode(@volume_number).pack("C*")
|
|
103
|
+
archive_flags |= VOLUME_NUMBER_FLAG
|
|
104
|
+
volume_number = @volume_number - 1
|
|
107
105
|
end
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# Extra areas are not written: MainHeader does not
|
|
112
|
-
# model them and the omnizip-rs reference implements
|
|
113
|
-
# RAR5 reading only, so there is no writer-side spec
|
|
114
|
-
# to port. Volumes work without extras (the field is
|
|
115
|
-
# optional).
|
|
116
|
-
|
|
117
|
-
@io.write(header.encode)
|
|
107
|
+
@io.write(MainHeader.new(archive_flags: archive_flags,
|
|
108
|
+
volume_number: volume_number).encode)
|
|
118
109
|
end
|
|
119
110
|
|
|
120
111
|
# Write file data (header + compressed data)
|
|
@@ -135,18 +126,10 @@ module Omnizip
|
|
|
135
126
|
def write_end_header
|
|
136
127
|
raise "Volume not open" unless @io
|
|
137
128
|
|
|
138
|
-
#
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
# EndHeader always encodes the plain END_OF_ARCHIVE
|
|
143
|
-
# marker: the RAR5 spec makes the volume flag optional,
|
|
144
|
-
# readers position the marker inside the volume
|
|
145
|
-
# sequence, and the omnizip-rs reference (read-side)
|
|
146
|
-
# carries no writer-side volume-flag encoding to port.
|
|
147
|
-
header = EndHeader.new
|
|
148
|
-
|
|
149
|
-
@io.write(header.encode)
|
|
129
|
+
# End-of-archive flags: 0x0001 marks a volume with more
|
|
130
|
+
# volumes following; the last volume ends with 0.
|
|
131
|
+
end_flags = @is_last ? 0 : VOLUME_END_FLAG
|
|
132
|
+
@io.write(EndHeader.new(end_flags: end_flags).encode)
|
|
150
133
|
end
|
|
151
134
|
|
|
152
135
|
# Generate volume filename from base name
|
|
@@ -6,57 +6,45 @@ module Omnizip
|
|
|
6
6
|
module Rar5
|
|
7
7
|
# Variable-length integer encoding/decoding for RAR5 format
|
|
8
8
|
module VINT
|
|
9
|
-
# Encode integer as VINT bytes
|
|
10
|
-
#
|
|
9
|
+
# Encode integer as VINT bytes per the RAR 5.0 spec: one or
|
|
10
|
+
# more bytes, each carrying 7 data bits starting with the
|
|
11
|
+
# least significant group; the high bit of every byte but
|
|
12
|
+
# the last is the continuation flag.
|
|
13
|
+
#
|
|
14
|
+
# @param value [Integer] Value to encode (0 to 2^63)
|
|
11
15
|
# @return [Array<Integer>] VINT bytes
|
|
12
16
|
def self.encode(value)
|
|
13
|
-
|
|
17
|
+
raise ArgumentError, "VINT cannot encode negative values" if value.negative?
|
|
14
18
|
|
|
15
19
|
bytes = []
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# First byte: continuation bits + high bits
|
|
24
|
-
first_byte = (0xFF << (9 - byte_count)) & 0xFF
|
|
25
|
-
first_byte |= (value >> (8 * (byte_count - 1))) & 0x7F
|
|
26
|
-
bytes << first_byte
|
|
27
|
-
|
|
28
|
-
# Remaining bytes
|
|
29
|
-
(byte_count - 1).downto(1) do |i|
|
|
30
|
-
bytes << ((value >> (8 * (i - 1))) & 0xFF)
|
|
20
|
+
loop do
|
|
21
|
+
byte = value & 0x7F
|
|
22
|
+
value >>= 7
|
|
23
|
+
byte |= 0x80 if value.positive?
|
|
24
|
+
bytes << byte
|
|
25
|
+
break if value.zero?
|
|
31
26
|
end
|
|
32
27
|
|
|
33
28
|
bytes
|
|
34
29
|
end
|
|
35
30
|
|
|
36
|
-
# Decode VINT from IO stream
|
|
31
|
+
# Decode VINT from IO stream (spec encoding, mirroring
|
|
32
|
+
# BlockParser#read_vint)
|
|
33
|
+
#
|
|
37
34
|
# @param io [IO] Input stream
|
|
38
35
|
# @return [Integer] Decoded value
|
|
39
36
|
def self.decode(io)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
mask >>= 1
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Extract value from first byte
|
|
52
|
-
value = first_byte & (0xFF >> (byte_count + 1))
|
|
53
|
-
|
|
54
|
-
# Read remaining bytes
|
|
55
|
-
byte_count.times do
|
|
56
|
-
value = (value << 8) | io.readbyte
|
|
37
|
+
result = 0
|
|
38
|
+
shift = 0
|
|
39
|
+
loop do
|
|
40
|
+
byte = io.readbyte
|
|
41
|
+
result |= (byte & 0x7F) << shift
|
|
42
|
+
break if byte.nobits?(0x80)
|
|
43
|
+
|
|
44
|
+
shift += 7
|
|
57
45
|
end
|
|
58
46
|
|
|
59
|
-
|
|
47
|
+
result
|
|
60
48
|
end
|
|
61
49
|
end
|
|
62
50
|
end
|
|
@@ -177,7 +177,10 @@ module Omnizip
|
|
|
177
177
|
write_signature(f)
|
|
178
178
|
write_main_header(f)
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
# Solid mode needs a RAR-compatible LZSS encoder; until
|
|
181
|
+
# one lands, fall back to independent STORE entries so
|
|
182
|
+
# the archive is actually extractable by unrar.
|
|
183
|
+
if @options[:solid] && Compression::Lzss.available?
|
|
181
184
|
write_solid_block(f, @files)
|
|
182
185
|
else
|
|
183
186
|
@files.each do |file|
|
|
@@ -251,6 +254,7 @@ module Omnizip
|
|
|
251
254
|
file_size: file_info[:size],
|
|
252
255
|
compressed_size: (idx.zero? ? result[:compressed_size] : 0), # Only first file has compressed size
|
|
253
256
|
compression_method: compression_method,
|
|
257
|
+
dict_size: dictionary_size_for(compression_method),
|
|
254
258
|
mtime: @options[:include_mtime] ? file[:mtime] : nil,
|
|
255
259
|
crc32: nil, # No CRC32 for solid/LZMA
|
|
256
260
|
)
|
|
@@ -307,12 +311,13 @@ module Omnizip
|
|
|
307
311
|
if @encryption_manager
|
|
308
312
|
encryption_result = @encryption_manager.encrypt_file_data(compressed_data)
|
|
309
313
|
final_data = encryption_result[:encrypted_data]
|
|
310
|
-
#
|
|
311
|
-
#
|
|
312
|
-
#
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
# Persist the salt/IV as the RAR5 file encryption extra
|
|
315
|
+
# record (spec type 0x01) — without it the archive
|
|
316
|
+
# cannot be decrypted by anything.
|
|
317
|
+
enc_record = build_encryption_extra_record(
|
|
318
|
+
encryption_result[:header],
|
|
319
|
+
)
|
|
320
|
+
extra_area = extra_area ? extra_area + enc_record : enc_record
|
|
316
321
|
else
|
|
317
322
|
final_data = compressed_data
|
|
318
323
|
end
|
|
@@ -332,6 +337,7 @@ module Omnizip
|
|
|
332
337
|
file_size: data.bytesize,
|
|
333
338
|
compressed_size: final_data.bytesize,
|
|
334
339
|
compression_method: compression_method,
|
|
340
|
+
dict_size: dictionary_size_for(compression_method),
|
|
335
341
|
mtime: @options[:include_mtime] ? file[:mtime] : nil,
|
|
336
342
|
crc32: file_crc32,
|
|
337
343
|
extra_area: extra_area,
|
|
@@ -356,6 +362,22 @@ module Omnizip
|
|
|
356
362
|
#
|
|
357
363
|
# @param data [String] File data
|
|
358
364
|
# @return [Integer] Compression method ID
|
|
365
|
+
# Dictionary size matching the compression modules' level
|
|
366
|
+
# table (128 KB << N), for the file header's dictionary
|
|
367
|
+
# bits. STORE needs no dictionary.
|
|
368
|
+
def dictionary_size_for(method)
|
|
369
|
+
return 0 if method == Compression::Store::METHOD
|
|
370
|
+
|
|
371
|
+
1 << case method
|
|
372
|
+
when 1 then 18 # 256 KB
|
|
373
|
+
when 2 then 20 # 1 MB
|
|
374
|
+
when 3 then 22 # 4 MB
|
|
375
|
+
when 4 then 23 # 8 MB
|
|
376
|
+
when 5 then 24 # 16 MB
|
|
377
|
+
else 22
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
359
381
|
def select_compression_method(data)
|
|
360
382
|
case @options[:compression]
|
|
361
383
|
when :store
|
|
@@ -367,7 +389,9 @@ module Omnizip
|
|
|
367
389
|
if Compression::Lzss.available?
|
|
368
390
|
Compression::Lzss.method_id(level)
|
|
369
391
|
else
|
|
370
|
-
#
|
|
392
|
+
warn "omnizip: RAR5 #{@options[:compression] == :lzma ? 'lzma' : 'lzss'} " \
|
|
393
|
+
"compression is not interoperable yet; storing data " \
|
|
394
|
+
"uncompressed instead"
|
|
371
395
|
Compression::Store::METHOD
|
|
372
396
|
end
|
|
373
397
|
when :auto
|
|
@@ -418,15 +442,27 @@ module Omnizip
|
|
|
418
442
|
def build_compression_extra_area(properties)
|
|
419
443
|
return nil if properties.nil?
|
|
420
444
|
|
|
421
|
-
|
|
445
|
+
# Extra record format per the RAR 5.0 spec: Size(vint),
|
|
446
|
+
# Type(vint), Data. Type 0x03 carries the compression
|
|
447
|
+
# parameters.
|
|
448
|
+
record = VINT.encode(0x03) + properties.bytes
|
|
449
|
+
VINT.encode(record.length) + record
|
|
450
|
+
end
|
|
422
451
|
|
|
423
|
-
|
|
424
|
-
|
|
452
|
+
# File encryption extra record (spec type 0x01): AES-256
|
|
453
|
+
# parameters the reader needs to derive the key and IV.
|
|
454
|
+
def build_encryption_extra_record(header)
|
|
455
|
+
require "base64"
|
|
425
456
|
|
|
426
|
-
|
|
427
|
-
|
|
457
|
+
kdf_count = Math.log2(header.kdf_iterations).round
|
|
458
|
+
record = [].concat(VINT.encode(0x01)) # record type
|
|
459
|
+
.concat(VINT.encode(header.version)) # 0 = AES-256
|
|
460
|
+
.concat(VINT.encode(0)) # flags: no check data
|
|
461
|
+
record << kdf_count # 1 byte, log2
|
|
462
|
+
record.concat(Base64.strict_decode64(header.salt).bytes)
|
|
463
|
+
record.concat(Base64.strict_decode64(header.iv).bytes)
|
|
428
464
|
|
|
429
|
-
|
|
465
|
+
(VINT.encode(record.length) + record).pack("C*")
|
|
430
466
|
end
|
|
431
467
|
|
|
432
468
|
# Generate PAR2 recovery files for archives
|
data/lib/omnizip/formats/rar.rb
CHANGED
|
@@ -37,10 +37,12 @@ module Omnizip
|
|
|
37
37
|
autoload :VINT, "omnizip/formats/rar/rar5/vint"
|
|
38
38
|
autoload :CRC32, "omnizip/formats/rar/rar5/crc32"
|
|
39
39
|
autoload :Header, "omnizip/formats/rar/rar5/header"
|
|
40
|
+
# MainHeader/FileHeader live in header.rb directly under Rar5;
|
|
41
|
+
# without their own autoload entries, referencing them only
|
|
42
|
+
# works if something else loaded the file first.
|
|
43
|
+
autoload :MainHeader, "omnizip/formats/rar/rar5/header"
|
|
44
|
+
autoload :FileHeader, "omnizip/formats/rar/rar5/header"
|
|
40
45
|
autoload :Writer, "omnizip/formats/rar/rar5/writer"
|
|
41
|
-
autoload :Reader, "omnizip/formats/rar/rar5/reader"
|
|
42
|
-
autoload :Compressor, "omnizip/formats/rar/rar5/compressor"
|
|
43
|
-
autoload :Decompressor, "omnizip/formats/rar/rar5/decompressor"
|
|
44
46
|
# RAR5 compression
|
|
45
47
|
module Compression
|
|
46
48
|
autoload :Store, "omnizip/formats/rar/rar5/compression/store"
|
|
@@ -121,6 +121,19 @@ module Omnizip
|
|
|
121
121
|
#
|
|
122
122
|
# @param num_items [Integer] Number of items in bit vector
|
|
123
123
|
# @return [Array<Boolean>] Bit vector
|
|
124
|
+
# Raw MSB-first bit vector without an all-defined marker
|
|
125
|
+
# (kEmptyStream/kEmptyFile/kAnti layout)
|
|
126
|
+
def read_raw_bit_vector(num_items)
|
|
127
|
+
num_bytes = (num_items + 7) / 8
|
|
128
|
+
raise EOFError if @position + num_bytes > @data.bytesize
|
|
129
|
+
|
|
130
|
+
bits_data = @data[@position, num_bytes]
|
|
131
|
+
@position += num_bytes
|
|
132
|
+
decode_bit_vector(bits_data, num_items)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Bit vector with an all-defined marker byte (kCRC/kWinAttrib
|
|
136
|
+
# defined-bits layout)
|
|
124
137
|
def read_bit_vector(num_items)
|
|
125
138
|
all_defined = read_byte
|
|
126
139
|
num_bytes = (num_items + 7) / 8
|
|
@@ -519,11 +532,11 @@ module Omnizip
|
|
|
519
532
|
# @param entries [Array<Models::FileEntry>] File entries
|
|
520
533
|
def read_empty_stream(entries)
|
|
521
534
|
skip_size
|
|
522
|
-
|
|
535
|
+
# kEmptyStream carries a RAW bit vector — no all-defined
|
|
536
|
+
# marker byte (unlike kCRC/kWinAttrib)
|
|
537
|
+
empty_stream = read_raw_bit_vector(entries.size)
|
|
523
538
|
entries.each_with_index do |entry, i|
|
|
524
|
-
|
|
525
|
-
# Convert to boolean: has_stream = (bit == 0)
|
|
526
|
-
entry.has_stream = (empty_stream[i].zero?)
|
|
539
|
+
entry.has_stream = empty_stream[i].zero?
|
|
527
540
|
entry.is_dir = (empty_stream[i] == 1)
|
|
528
541
|
end
|
|
529
542
|
end
|
|
@@ -534,9 +547,11 @@ module Omnizip
|
|
|
534
547
|
def read_empty_file(entries)
|
|
535
548
|
skip_size
|
|
536
549
|
empty_files = entries.reject(&:has_stream)
|
|
537
|
-
empty_bits =
|
|
550
|
+
empty_bits = read_raw_bit_vector(empty_files.size)
|
|
538
551
|
empty_files.each_with_index do |entry, i|
|
|
539
|
-
|
|
552
|
+
# Bit set = zero-byte FILE (not a directory)
|
|
553
|
+
entry.is_empty = empty_bits[i] == 1
|
|
554
|
+
entry.is_dir = false if entry.is_empty
|
|
540
555
|
end
|
|
541
556
|
end
|
|
542
557
|
|
|
@@ -546,7 +561,7 @@ module Omnizip
|
|
|
546
561
|
def read_anti(entries)
|
|
547
562
|
skip_size
|
|
548
563
|
anti_files = entries.select { |e| !e.has_stream && !e.is_empty }
|
|
549
|
-
anti_bits =
|
|
564
|
+
anti_bits = read_raw_bit_vector(anti_files.size)
|
|
550
565
|
anti_files.each_with_index do |entry, i|
|
|
551
566
|
entry.is_anti = anti_bits[i]
|
|
552
567
|
end
|