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
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Omnizip
|
|
4
|
+
module Formats
|
|
5
|
+
# Standalone RAR5 reader/writer tree (see Formats::Rar for the
|
|
6
|
+
# primary RAR support). Classes under this namespace load lazily;
|
|
7
|
+
# require "omnizip" first so sibling namespaces resolve.
|
|
8
|
+
module Rar5
|
|
9
|
+
autoload :Compressor, "omnizip/formats/rar5/compressor"
|
|
10
|
+
autoload :Decompressor, "omnizip/formats/rar5/decompressor"
|
|
11
|
+
autoload :Reader, "omnizip/formats/rar5/reader"
|
|
12
|
+
autoload :Writer, "omnizip/formats/rar5/writer"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -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
|
|
@@ -60,7 +60,9 @@ module Omnizip
|
|
|
60
60
|
entry.source_path = nil
|
|
61
61
|
entry.size = data_str.bytesize
|
|
62
62
|
entry.mtime = Time.now
|
|
63
|
-
|
|
63
|
+
# Zero-length data has no stream: readers identify it as an
|
|
64
|
+
# empty file through kEmptyStream + kEmptyFile.
|
|
65
|
+
entry.has_stream = data_str.bytesize.positive?
|
|
64
66
|
entry.inline_data = data_str
|
|
65
67
|
entry.compression_options = options
|
|
66
68
|
@entries << entry
|
|
@@ -280,7 +282,7 @@ module Omnizip
|
|
|
280
282
|
end
|
|
281
283
|
|
|
282
284
|
def build_solid_streams_info(metadata, unpack_size, compressed_size,
|
|
283
|
-
|
|
285
|
+
_num_files)
|
|
284
286
|
# kPackInfo property (0x06)
|
|
285
287
|
metadata << [PropertyId::PACK_INFO].pack("C")
|
|
286
288
|
metadata << write_number(0) # Pack position
|
|
@@ -313,37 +315,44 @@ num_files)
|
|
|
313
315
|
# kEnd for UnpackInfo
|
|
314
316
|
metadata << [PropertyId::K_END].pack("C")
|
|
315
317
|
|
|
316
|
-
# kSubStreamsInfo
|
|
318
|
+
# kSubStreamsInfo describes exactly the entries WITH a
|
|
319
|
+
# stream: directory entries (empty-stream) are excluded,
|
|
320
|
+
# or readers reject the header.
|
|
317
321
|
metadata << [PropertyId::SUBSTREAMS_INFO].pack("C")
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
322
|
+
stream_files = @entries.select(&:has_stream?)
|
|
323
|
+
# Write the explicit form whenever any entry lacks a
|
|
324
|
+
# stream (directory entries): without kNumUnpackStreams,
|
|
325
|
+
# readers assume one substream per file and reject the
|
|
326
|
+
# digest count.
|
|
327
|
+
if stream_files.size > 1 || stream_files.size != @entries.size
|
|
328
|
+
|
|
329
|
+
# NUM_UNPACK_STREAM - number of substreams in this folder
|
|
321
330
|
metadata << [PropertyId::NUM_UNPACK_STREAM].pack("C")
|
|
322
|
-
metadata << write_number(
|
|
331
|
+
metadata << write_number(stream_files.size)
|
|
323
332
|
|
|
324
|
-
# SIZE - size of each
|
|
325
|
-
# Per 7-Zip spec: only write numSubstreams-1 sizes, last
|
|
326
|
-
# from folder's unpack size minus sum
|
|
333
|
+
# SIZE - size of each substream except the last one
|
|
334
|
+
# Per 7-Zip spec: only write numSubstreams-1 sizes, last
|
|
335
|
+
# is calculated from folder's unpack size minus the sum
|
|
327
336
|
metadata << [PropertyId::SIZE].pack("C")
|
|
328
|
-
|
|
337
|
+
stream_files[0..-2].each do |entry|
|
|
329
338
|
metadata << write_number(entry.size)
|
|
330
339
|
end
|
|
331
340
|
|
|
332
341
|
# CRCs
|
|
333
342
|
metadata << [PropertyId::CRC].pack("C")
|
|
334
343
|
metadata << [1].pack("C") # All defined
|
|
335
|
-
|
|
344
|
+
stream_files.each do |entry|
|
|
336
345
|
# Use 0 for entries without CRC (empty files)
|
|
337
346
|
crc = entry.crc || 0
|
|
338
347
|
metadata << [crc].pack("V")
|
|
339
348
|
end
|
|
340
349
|
|
|
341
350
|
else
|
|
342
|
-
# Single
|
|
351
|
+
# Single substream: CRC goes in SubStreamsInfo
|
|
343
352
|
metadata << [PropertyId::CRC].pack("C")
|
|
344
353
|
metadata << [1].pack("C") # All defined
|
|
345
354
|
# Use 0 for entries without CRC (empty files)
|
|
346
|
-
crc =
|
|
355
|
+
crc = stream_files.first&.crc || 0
|
|
347
356
|
metadata << [crc].pack("V")
|
|
348
357
|
end
|
|
349
358
|
metadata << [PropertyId::K_END].pack("C")
|
|
@@ -396,12 +405,13 @@ num_files)
|
|
|
396
405
|
# kEnd for UnpackInfo
|
|
397
406
|
metadata << [PropertyId::K_END].pack("C")
|
|
398
407
|
|
|
399
|
-
# kSubStreamsInfo -
|
|
408
|
+
# kSubStreamsInfo - CRC for each substream. Each folder
|
|
409
|
+
# holds exactly one substream here, so digests cover only
|
|
410
|
+
# the entries WITH a stream (directory entries have none).
|
|
400
411
|
metadata << [PropertyId::SUBSTREAMS_INFO].pack("C")
|
|
401
412
|
metadata << [PropertyId::CRC].pack("C")
|
|
402
413
|
metadata << [1].pack("C") # All defined
|
|
403
|
-
@entries.each do |entry|
|
|
404
|
-
# Use 0 for entries without CRC (empty files)
|
|
414
|
+
@entries.select(&:has_stream?).each do |entry|
|
|
405
415
|
crc = entry.crc || 0
|
|
406
416
|
metadata << [crc].pack("V")
|
|
407
417
|
end
|
|
@@ -438,6 +448,34 @@ num_files)
|
|
|
438
448
|
# Number of files
|
|
439
449
|
metadata << write_number(@entries.size)
|
|
440
450
|
|
|
451
|
+
# Build EMPTY_STREAM property (0x0E) — written first, like
|
|
452
|
+
# 7-Zip itself: readers derive the directory flag from it;
|
|
453
|
+
# without it, directory entries are extracted as (invalid)
|
|
454
|
+
# files. Format: [EMPTY_STREAM] [size] [bit vector]
|
|
455
|
+
empty_bits = @entries.map { |e| !e.has_stream? }
|
|
456
|
+
if empty_bits.any?
|
|
457
|
+
# kEmptyStream carries the raw bit bytes only — NO
|
|
458
|
+
# all-defined marker byte (verified against 7-Zip's own
|
|
459
|
+
# -mhc=off output; a marker byte here shifts the whole
|
|
460
|
+
# header for strict readers).
|
|
461
|
+
empty_data = write_bit_bytes(empty_bits)
|
|
462
|
+
metadata << [PropertyId::EMPTY_STREAM].pack("C")
|
|
463
|
+
metadata << write_number(empty_data.bytesize)
|
|
464
|
+
metadata << empty_data
|
|
465
|
+
|
|
466
|
+
# kEmptyFile distinguishes zero-byte files from
|
|
467
|
+
# directories among the empty-stream entries.
|
|
468
|
+
empty_file_bits = @entries.map do |e|
|
|
469
|
+
!e.has_stream? && !e.is_dir
|
|
470
|
+
end
|
|
471
|
+
if empty_file_bits.any?
|
|
472
|
+
ef_data = write_bit_bytes(empty_file_bits)
|
|
473
|
+
metadata << [PropertyId::EMPTY_FILE].pack("C")
|
|
474
|
+
metadata << write_number(ef_data.bytesize)
|
|
475
|
+
metadata << ef_data
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
441
479
|
# Build NAME property (0x11)
|
|
442
480
|
# Format: [NAME] [size] [external] [UTF-16LE names with null terminators]
|
|
443
481
|
name_data = String.new(encoding: "BINARY")
|
|
@@ -474,8 +512,8 @@ num_files)
|
|
|
474
512
|
# Build WIN_ATTRIB property (0x15) - comes after MTIME per 7-Zip spec
|
|
475
513
|
# Format: [WIN_ATTRIB] [size] [defined bits] [external] [attributes]
|
|
476
514
|
attr_data = String.new(encoding: "BINARY")
|
|
477
|
-
@entries.each do |
|
|
478
|
-
attr_data << FILE_ATTRIBUTE_ARCHIVE
|
|
515
|
+
@entries.each do |entry|
|
|
516
|
+
attr_data << [entry.attributes || FILE_ATTRIBUTE_ARCHIVE.unpack1("V")].pack("V")
|
|
479
517
|
end
|
|
480
518
|
|
|
481
519
|
metadata << [PropertyId::WIN_ATTRIB].pack("C")
|
|
@@ -671,6 +709,27 @@ next_header_data)
|
|
|
671
709
|
# 110xxxxx BYTE y[2] : value = (xxxxx << 16) + y
|
|
672
710
|
# 1110xxxx BYTE y[3] : value = (xxxx << 24) + y
|
|
673
711
|
# ...up to 8 bytes total
|
|
712
|
+
# Raw MSB-first packed bit bytes, no marker
|
|
713
|
+
def write_bit_bytes(bits)
|
|
714
|
+
bytes = Array.new((bits.size + 7) / 8, 0)
|
|
715
|
+
bits.each_with_index do |bit, i|
|
|
716
|
+
bytes[i / 8] |= (1 << (7 - (i % 8))) if bit
|
|
717
|
+
end
|
|
718
|
+
bytes.pack("C*")
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
# Bit vector with an all-defined marker byte, MSB-first
|
|
722
|
+
# packing (same layout as HeaderWriter's)
|
|
723
|
+
def write_bit_vector(bits)
|
|
724
|
+
return [1].pack("C") if bits.all?
|
|
725
|
+
|
|
726
|
+
bytes = Array.new((bits.size + 7) / 8, 0)
|
|
727
|
+
bits.each_with_index do |bit, i|
|
|
728
|
+
bytes[i / 8] |= (1 << (7 - (i % 8))) if bit
|
|
729
|
+
end
|
|
730
|
+
[0].pack("C") + bytes.pack("C*")
|
|
731
|
+
end
|
|
732
|
+
|
|
674
733
|
def write_number(value)
|
|
675
734
|
# Single byte encoding (0-127)
|
|
676
735
|
return [value].pack("C") if value < 0x80
|
data/lib/omnizip/formats.rb
CHANGED
|
@@ -7,6 +7,8 @@ module Omnizip
|
|
|
7
7
|
autoload :SevenZip, "omnizip/formats/seven_zip"
|
|
8
8
|
autoload :Zip, "omnizip/formats/zip"
|
|
9
9
|
autoload :Rar, "omnizip/formats/rar"
|
|
10
|
+
autoload :Rar3, "omnizip/formats/rar3"
|
|
11
|
+
autoload :Rar5, "omnizip/formats/rar5"
|
|
10
12
|
autoload :Tar, "omnizip/formats/tar"
|
|
11
13
|
autoload :Gzip, "omnizip/formats/gzip"
|
|
12
14
|
autoload :Bzip2File, "omnizip/formats/bzip2_file"
|
data/lib/omnizip/version.rb
CHANGED
data/readme-docs/api-usage.adoc
CHANGED
|
@@ -418,13 +418,14 @@ Full support for creating and extracting .7z archives.
|
|
|
418
418
|
[source,ruby]
|
|
419
419
|
----
|
|
420
420
|
# Create archive
|
|
421
|
-
|
|
422
|
-
writer.add_file('input.txt')
|
|
423
|
-
writer.add_file('data.bin')
|
|
424
|
-
|
|
421
|
+
Omnizip::Formats::SevenZip.create('archive.7z') do |writer|
|
|
422
|
+
writer.add_file('input.txt')
|
|
423
|
+
writer.add_file('data.bin')
|
|
424
|
+
end
|
|
425
425
|
|
|
426
426
|
# Extract archive
|
|
427
427
|
reader = Omnizip::Formats::SevenZip::Reader.new('archive.7z')
|
|
428
|
+
reader.open
|
|
428
429
|
reader.extract_all('output_dir/')
|
|
429
430
|
reader.close
|
|
430
431
|
|
|
@@ -198,12 +198,13 @@ end
|
|
|
198
198
|
|
|
199
199
|
[source,ruby]
|
|
200
200
|
----
|
|
201
|
-
writer = Omnizip::Formats::SevenZip::Writer.new('archive.7z'
|
|
202
|
-
|
|
203
|
-
|
|
201
|
+
writer = Omnizip::Formats::SevenZip::Writer.new('archive.7z',
|
|
202
|
+
algorithm: :lzma2,
|
|
203
|
+
level: 9
|
|
204
|
+
)
|
|
204
205
|
writer.add_file('file1.txt')
|
|
205
206
|
writer.add_file('file2.txt')
|
|
206
|
-
writer.
|
|
207
|
+
writer.write
|
|
207
208
|
----
|
|
208
209
|
|
|
209
210
|
=== Adapter Pattern
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omnizip
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.39
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -324,6 +324,8 @@ files:
|
|
|
324
324
|
- lib/omnizip/archive/reader_session.rb
|
|
325
325
|
- lib/omnizip/archive_handler.rb
|
|
326
326
|
- lib/omnizip/archive_handlers.rb
|
|
327
|
+
- lib/omnizip/archive_handlers/cpio_handler.rb
|
|
328
|
+
- lib/omnizip/archive_handlers/iso_handler.rb
|
|
327
329
|
- lib/omnizip/archive_handlers/rar_handler.rb
|
|
328
330
|
- lib/omnizip/archive_handlers/seven_zip_handler.rb
|
|
329
331
|
- lib/omnizip/archive_handlers/tar_handler.rb
|
|
@@ -502,10 +504,12 @@ files:
|
|
|
502
504
|
- lib/omnizip/formats/rar/recovery_record.rb
|
|
503
505
|
- lib/omnizip/formats/rar/volume_manager.rb
|
|
504
506
|
- lib/omnizip/formats/rar/writer.rb
|
|
507
|
+
- lib/omnizip/formats/rar3.rb
|
|
505
508
|
- lib/omnizip/formats/rar3/compressor.rb
|
|
506
509
|
- lib/omnizip/formats/rar3/decompressor.rb
|
|
507
510
|
- lib/omnizip/formats/rar3/reader.rb
|
|
508
511
|
- lib/omnizip/formats/rar3/writer.rb
|
|
512
|
+
- lib/omnizip/formats/rar5.rb
|
|
509
513
|
- lib/omnizip/formats/rar5/compressor.rb
|
|
510
514
|
- lib/omnizip/formats/rar5/decompressor.rb
|
|
511
515
|
- lib/omnizip/formats/rar5/reader.rb
|