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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -9
  3. data/README.adoc +108 -140
  4. data/config/formats/rar3_spec.yml +1 -0
  5. data/docs/guides/archive-formats/gzip-format.adoc +2 -4
  6. data/docs/guides/archive-formats/ole-format.adoc +54 -219
  7. data/docs/guides/archive-formats/rar5.adoc +15 -15
  8. data/docs/guides/archive-formats/rpm-format.adoc +48 -189
  9. data/docs/reference/api/overview.adoc +16 -17
  10. data/docs/troubleshooting/index.adoc +1 -4
  11. data/lib/omnizip/archive_handler.rb +2 -0
  12. data/lib/omnizip/archive_handlers/cpio_handler.rb +47 -0
  13. data/lib/omnizip/archive_handlers/iso_handler.rb +54 -0
  14. data/lib/omnizip/archive_handlers.rb +2 -0
  15. data/lib/omnizip/cli.rb +25 -0
  16. data/lib/omnizip/convenience.rb +6 -4
  17. data/lib/omnizip/formats/iso/directory_builder.rb +24 -0
  18. data/lib/omnizip/formats/iso/reader.rb +2 -2
  19. data/lib/omnizip/formats/iso/writer.rb +20 -4
  20. data/lib/omnizip/formats/iso.rb +3 -1
  21. data/lib/omnizip/formats/rar/block_parser.rb +18 -8
  22. data/lib/omnizip/formats/rar/constants.rb +4 -0
  23. data/lib/omnizip/formats/rar/decompressor.rb +12 -8
  24. data/lib/omnizip/formats/rar/header.rb +7 -15
  25. data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +5 -3
  26. data/lib/omnizip/formats/rar/rar5/header.rb +50 -33
  27. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +17 -34
  28. data/lib/omnizip/formats/rar/rar5/vint.rb +25 -37
  29. data/lib/omnizip/formats/rar/rar5/writer.rb +50 -14
  30. data/lib/omnizip/formats/rar/reader.rb +18 -14
  31. data/lib/omnizip/formats/rar/writer.rb +121 -136
  32. data/lib/omnizip/formats/rar.rb +5 -11
  33. data/lib/omnizip/formats/rar3/reader.rb +11 -50
  34. data/lib/omnizip/formats/rar3/writer.rb +13 -10
  35. data/lib/omnizip/formats/rar3.rb +15 -0
  36. data/lib/omnizip/formats/rar5/reader.rb +8 -10
  37. data/lib/omnizip/formats/rar5.rb +15 -0
  38. data/lib/omnizip/formats/seven_zip/parser.rb +22 -7
  39. data/lib/omnizip/formats/seven_zip/writer.rb +78 -19
  40. data/lib/omnizip/formats.rb +2 -0
  41. data/lib/omnizip/version.rb +1 -1
  42. data/readme-docs/api-usage.adoc +5 -4
  43. data/readme-docs/architecture.adoc +5 -4
  44. 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
- empty_stream = read_bit_vector(entries.size)
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
- # Bit vector: 0 = has stream (file), 1 = empty (directory)
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 = read_bit_vector(empty_files.size)
550
+ empty_bits = read_raw_bit_vector(empty_files.size)
538
551
  empty_files.each_with_index do |entry, i|
539
- entry.is_empty = !empty_bits[i]
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 = read_bit_vector(anti_files.size)
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
- entry.has_stream = true
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
- num_files)
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 - for solid archives with multiple files
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
- if num_files > 1
319
-
320
- # NUM_UNPACK_STREAM - number of files in this folder
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(num_files)
331
+ metadata << write_number(stream_files.size)
323
332
 
324
- # SIZE - size of each file's data (except the last one)
325
- # Per 7-Zip spec: only write numSubstreams-1 sizes, last is calculated
326
- # from folder's unpack size minus sum of written sizes
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
- @entries[0..-2].each do |entry| # All except last
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
- @entries.each do |entry|
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 file: CRC goes in SubStreamsInfo
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 = @entries.first&.crc || 0
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 - CRCs for each file
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 |_entry|
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
@@ -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"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.3.37"
4
+ VERSION = "0.3.39"
5
5
  end
@@ -418,13 +418,14 @@ Full support for creating and extracting .7z archives.
418
418
  [source,ruby]
419
419
  ----
420
420
  # Create archive
421
- writer = Omnizip::Formats::SevenZip::Writer.new('archive.7z')
422
- writer.add_file('input.txt')
423
- writer.add_file('data.bin')
424
- writer.close
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
- writer.algorithm = :lzma2
203
- writer.level = 9
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.close
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.37
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