omnizip 0.3.48 → 0.3.50

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ff2a922c26dc80694f6167b0d46e16d28fe34fa9a0cdbcea12f6f5dc57085b6
4
- data.tar.gz: f260edbb81b37ba0824fb9a896fc1668a60d3e74ffeba44821670d4304dd065d
3
+ metadata.gz: 6aa49d6ab2a370eda775a80206da80220936025ece39c3e121471815b309adbd
4
+ data.tar.gz: 60ab5eb1c7bcd3243f71aed658a85e84b2b37a02d061bf09156f0dd82c18d536
5
5
  SHA512:
6
- metadata.gz: 0b47b5b5702166234e3c5e5cc132f3f944837904a9f4ce91a560e6c4207de329ec8d1e5ae01028e1b872c4dfbf5fda8b0ffee981ac9698cf500bc114d0a0ae65
7
- data.tar.gz: e788bec8df533ea0417eb8db6e762a89ac17908e676558132eb127e1aa18af4c64bdc6da2151d50d772a4754c4a81ced191fa4b1b750fb6df27683d031ad237b
6
+ metadata.gz: 8e4cc8c3b167c07112ad5f45f3a870eb24b8ffd946cc7f9fe6c4b10bfe376e7efc5a0c975287027646d9bc7aef3f419e67eb1bd40e38824eb3e93219a3f78603
7
+ data.tar.gz: 7fe55f7e975c40eff0c2109167a0e23fa99f11949489a29f7555632a340b3ea16983091f09bfa6cd948e3351501fd66fb438d3ad90a40953ef514721fc0138f0
data/CHANGELOG.md CHANGED
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.49] - 2026-08-31
11
+
12
+ ### Added
13
+ - Read routes for `.rpm`, `.xar`, and `.msi`: the formats had
14
+ verified readers but no handler and no extension route, so
15
+ `list_archive('pkg.rpm')` silently fell through to the ZIP
16
+ reader. Three read-only handlers register them (extraction,
17
+ listing, entry reading; creation raises truthfully). `.msi`
18
+ streams are listed exactly as stored — MSI table-stream names
19
+ are mangled in an MSI-domain encoding the handler does not guess
20
+ at. The CLI `decompress` command learns the new extensions
21
+ (`.rpm` previously fell into the single-file LZMA path and failed
22
+ with `Invalid pb`).
23
+
24
+ ### Fixed
25
+ - The 7z writer silently ignored its documented `filters:` option
26
+ (preprocessing filters are decode-side only); it now warns that
27
+ it writes unfiltered data instead of pretending.
28
+
29
+ ## [0.3.48] - 2026-08-31
30
+
31
+ ### Changed
32
+ - `omnizip archive create` routes through the Archive facade: the
33
+ command's two hand-munged format branches collapse into one
34
+ `Archive.create` call — the handler registry picks each format's
35
+ writer and applies its option semantics, and the command only
36
+ translates CLI vocabulary (414 -> 290 lines). Verified for 7z
37
+ (7zz byte-identical), RAR5, RAR4, volume splitting, and verbose
38
+ output.
39
+
40
+ ### Fixed
41
+ - The 7z handler proxy dropped the Builder's explicit directory
42
+ entries (derive-from-paths no-op), so CLI-created archives lost
43
+ the `dir/` entries the previous writer path emitted. The proxy
44
+ maps name-only adds to `add_directory_entry`, which preserves the
45
+ trailing slash; the Buffer read proxy no longer double-slashes
46
+ such names.
47
+ - The facade denied RAR passwords (`PASSWORD_FORMATS` listed 7z
48
+ only) while the RAR5 writer produces unrar-decryptable encrypted
49
+ archives — `:rar` added (verified with `unrar -p`).
50
+ - RAR4 recovery guard crashed on explicit boolean options:
51
+ `false&.to_i` still calls `to_i` (safe navigation guards nil
52
+ only) — Numeric check now.
53
+ - RAR interop specs gate on the unrar command (through its resolved
54
+ path), not the gem wrapper: CI runners can carry the gem without
55
+ the binary on PATH.
56
+
10
57
  ## [0.3.47] - 2026-08-31
11
58
 
12
59
  ### Fixed
@@ -68,6 +68,9 @@ module Omnizip
68
68
  # file, which then self-registers at the bottom of the file.
69
69
  LAZY_LOAD_TRIGGERS = {
70
70
  zip: -> { Omnizip::ArchiveHandlers::ZipHandler },
71
+ rpm: -> { Omnizip::ArchiveHandlers::RpmHandler },
72
+ xar: -> { Omnizip::ArchiveHandlers::XarHandler },
73
+ ole: -> { Omnizip::ArchiveHandlers::OleHandler },
71
74
  tar: -> { Omnizip::ArchiveHandlers::TarHandler },
72
75
  seven_zip: -> { Omnizip::ArchiveHandlers::SevenZipHandler },
73
76
  rar: -> { Omnizip::ArchiveHandlers::RarHandler },
@@ -0,0 +1,37 @@
1
+ # frozen_string: true
2
+
3
+ module Omnizip
4
+ module ArchiveHandlers
5
+ # Read-only adapter for OLE compound documents ( MSI installers,
6
+ # legacy Office binaries): stream listing, reading, and
7
+ # extraction. Stream names are listed exactly as stored — MSI
8
+ # installers mangle their table-stream names in a
9
+ # MSI-domain encoding this handler does not decode.
10
+ class OleHandler
11
+ def create(_path, **_options)
12
+ raise Omnizip::UnsupportedFormatError,
13
+ "compound documents cannot be created through the " \
14
+ "convenience API; use Omnizip::Formats::Ole directly"
15
+ end
16
+
17
+ def extract_to(path, output_dir, **_)
18
+ Omnizip::Formats::Ole.extract(path, output_dir)
19
+ Dir.glob(File.join(output_dir, "**", "*"))
20
+ .select { |f| File.file?(f) }
21
+ end
22
+
23
+ def list(path, details: false, **_)
24
+ entries = Omnizip::Formats::Ole.list(path)
25
+ return entries unless details
26
+
27
+ entries.map { |name| { name: name, size: nil, directory: false } }
28
+ end
29
+
30
+ def read_entry(path, entry_name, **_)
31
+ Omnizip::Formats::Ole.read(path, entry_name)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ Omnizip::ArchiveHandler.register(:ole, Omnizip::ArchiveHandlers::OleHandler.new)
@@ -0,0 +1,45 @@
1
+ # frozen_string: true
2
+
3
+ require "tmpdir"
4
+
5
+ module Omnizip
6
+ module ArchiveHandlers
7
+ # Read-only adapter for the RPM package format: extraction and
8
+ # listing over the CPIO payload; package metadata lives in
9
+ # Formats::Rpm.info. RPM writing exists at the format level but
10
+ # is not exposed through the convenience archive API.
11
+ class RpmHandler
12
+ def create(_path, **_options)
13
+ raise Omnizip::UnsupportedFormatError,
14
+ "rpm archives cannot be created through the convenience " \
15
+ "API; use Omnizip::Formats::Rpm::Writer directly"
16
+ end
17
+
18
+ def extract_to(path, output_dir, **_)
19
+ Omnizip::Formats::Rpm.extract(path, output_dir)
20
+ Dir.glob(File.join(output_dir, "**", "*"))
21
+ .select { |f| File.file?(f) }
22
+ end
23
+
24
+ def list(path, details: false, **_)
25
+ entries = Omnizip::Formats::Rpm.list(path)
26
+ return entries unless details
27
+
28
+ entries.map { |name| { name: name, size: nil, directory: false } }
29
+ end
30
+
31
+ def read_entry(path, entry_name, **_)
32
+ Dir.mktmpdir("omnizip-rpm-entry") do |dir|
33
+ Omnizip::Formats::Rpm.extract(path, dir)
34
+ dest = File.join(dir, entry_name)
35
+ raise Errno::ENOENT, "Entry not found: #{entry_name}" unless
36
+ File.exist?(dest)
37
+
38
+ File.binread(dest)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ Omnizip::ArchiveHandler.register(:rpm, Omnizip::ArchiveHandlers::RpmHandler.new)
@@ -0,0 +1,49 @@
1
+ # frozen_string: true
2
+
3
+ require "tmpdir"
4
+
5
+ module Omnizip
6
+ module ArchiveHandlers
7
+ # Read-only adapter for the XAR archive format: extraction and
8
+ # listing over the XML TOC. XAR writing exists at the format
9
+ # level but is not exposed through the convenience archive API.
10
+ class XarHandler
11
+ def create(_path, **_options)
12
+ raise Omnizip::UnsupportedFormatError,
13
+ "xar archives cannot be created through the convenience " \
14
+ "API; use Omnizip::Formats::Xar.create directly"
15
+ end
16
+
17
+ def extract_to(path, output_dir, **_)
18
+ Omnizip::Formats::Xar.extract(path, output_dir)
19
+ Dir.glob(File.join(output_dir, "**", "*"))
20
+ .select { |f| File.file?(f) }
21
+ end
22
+
23
+ def list(path, details: false, **_)
24
+ entries = Omnizip::Formats::Xar.list(path)
25
+ if details
26
+ entries.map do |e|
27
+ { name: e.name, size: e.size,
28
+ directory: false }
29
+ end
30
+ else
31
+ entries.map(&:name)
32
+ end
33
+ end
34
+
35
+ def read_entry(path, entry_name, **_)
36
+ Dir.mktmpdir("omnizip-xar-entry") do |dir|
37
+ Omnizip::Formats::Xar.extract(path, dir)
38
+ dest = File.join(dir, entry_name)
39
+ raise Errno::ENOENT, "Entry not found: #{entry_name}" unless
40
+ File.exist?(dest)
41
+
42
+ File.binread(dest)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ Omnizip::ArchiveHandler.register(:xar, Omnizip::ArchiveHandlers::XarHandler.new)
@@ -10,6 +10,9 @@ module Omnizip
10
10
  autoload :SevenZipHandler, "omnizip/archive_handlers/seven_zip_handler"
11
11
  autoload :RarHandler, "omnizip/archive_handlers/rar_handler"
12
12
  autoload :CpioHandler, "omnizip/archive_handlers/cpio_handler"
13
+ autoload :RpmHandler, "omnizip/archive_handlers/rpm_handler"
14
+ autoload :XarHandler, "omnizip/archive_handlers/xar_handler"
15
+ autoload :OleHandler, "omnizip/archive_handlers/ole_handler"
13
16
  autoload :IsoHandler, "omnizip/archive_handlers/iso_handler"
14
17
  end
15
18
  end
@@ -80,7 +80,7 @@ module Omnizip
80
80
  end
81
81
  end
82
82
 
83
- ARCHIVE_EXTENSIONS = %w[.zip .7z .tar .rar .cpio .iso].freeze
83
+ ARCHIVE_EXTENSIONS = %w[.zip .7z .tar .rar .cpio .iso .rpm .xar .msi].freeze
84
84
 
85
85
  private
86
86
 
@@ -236,7 +236,7 @@ module Omnizip
236
236
  # creation raises truthfully instead (the format-level writers
237
237
  # exist for CPIO/ISO but are not part of the archive API; RAR
238
238
  # moved to the writable routes when its handler gained create).
239
- READ_ONLY_FORMAT_EXTENSIONS = [".iso", ".cpio"].freeze
239
+ READ_ONLY_FORMAT_EXTENSIONS = %w[.iso .cpio .rpm .xar .msi].freeze
240
240
 
241
241
  # Extensions whose format has a READ-ONLY handler: extraction and
242
242
  # listing route to it, while creation keeps raising.
@@ -244,6 +244,9 @@ module Omnizip
244
244
  ".rar" => :rar,
245
245
  ".cpio" => :cpio,
246
246
  ".iso" => :iso,
247
+ ".rpm" => :rpm,
248
+ ".xar" => :xar,
249
+ ".msi" => :ole,
247
250
  }.freeze
248
251
 
249
252
  # Extension -> single-file decompressor (stream interface).
@@ -45,5 +45,6 @@ module Omnizip
45
45
  # Register built-in strategies
46
46
  ConversionRegistry.register(ZipToSevenZipStrategy)
47
47
  ConversionRegistry.register(SevenZipToZipStrategy)
48
+ ConversionRegistry.register(ExtractRepackStrategy)
48
49
  end
49
50
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string: true
2
+
3
+ require "tmpdir"
4
+
5
+ module Omnizip
6
+ module Converter
7
+ # Fallback conversion strategy: extract any routed source format
8
+ # and repack the tree into any writable target format, both
9
+ # through the handler registry. Registered after the direct
10
+ # entry-copy strategies, which keep precedence for their pairs.
11
+ class ExtractRepackStrategy < ConversionStrategy
12
+ WRITABLE_FORMATS = %i[zip seven_zip tar rar].freeze
13
+
14
+ # Any routed source extension converts to any writable target
15
+ def self.can_convert?(source, _target)
16
+ !source_format_for(source).nil? &&
17
+ WRITABLE_FORMATS.include?(target_format_for(_target))
18
+ end
19
+
20
+ # Routed read-side format for an extension, or nil
21
+ def self.source_format_for(path)
22
+ ext = File.extname(path).downcase
23
+ Convenience::ARCHIVE_FORMAT_EXTENSIONS[ext] ||
24
+ Convenience::READ_ARCHIVE_FORMAT_EXTENSIONS[ext]
25
+ end
26
+
27
+ # Writable format for a target extension, or nil
28
+ def self.target_format_for(path)
29
+ Convenience::ARCHIVE_FORMAT_EXTENSIONS[File.extname(path).downcase]
30
+ end
31
+
32
+ def convert
33
+ start_time = Time.now
34
+
35
+ Dir.mktmpdir("omnizip_convert_repack") do |tmp|
36
+ extracted = Omnizip.extract_archive(source_path, tmp)
37
+
38
+ Omnizip::Archive.create(target_path,
39
+ format: self.class.target_format_for(target_path)) do |b|
40
+ Dir.glob(File.join(tmp, "**", "*")).each do |path|
41
+ next if File.directory?(path)
42
+
43
+ b.add_file(path, path.delete_prefix("#{tmp}/"))
44
+ end
45
+ end
46
+
47
+ entry_count = extracted.size
48
+ create_result(start_time, entry_count)
49
+ end
50
+ end
51
+
52
+ def source_format
53
+ self.class.source_format_for(source_path) || :unknown
54
+ end
55
+
56
+ def target_format
57
+ self.class.target_format_for(target_path) || :unknown
58
+ end
59
+ end
60
+ end
61
+ end
@@ -11,6 +11,8 @@ module Omnizip
11
11
  autoload :SevenZipToZipStrategy,
12
12
  "omnizip/converter/seven_zip_to_zip_strategy"
13
13
  autoload :ConversionRegistry, "omnizip/converter/conversion_registry"
14
+ autoload :ExtractRepackStrategy,
15
+ "omnizip/converter/extract_repack_strategy"
14
16
 
15
17
  class << self
16
18
  # Convert archive from one format to another
@@ -37,6 +37,16 @@ module Omnizip
37
37
  }.merge(options)
38
38
  @collector = FileCollector.new
39
39
  @entries = []
40
+
41
+ # Preprocessing filters are decode-side only: the write
42
+ # path does not apply them, so say so instead of silently
43
+ # ignoring the option
44
+ filters = @options[:filters]
45
+ return unless filters && !filters.empty?
46
+
47
+ warn "omnizip: the 7z writer does not apply filters " \
48
+ "(#{Array(filters).join(', ')}); writing unfiltered data. " \
49
+ "Filters apply when reading archives that contain them."
40
50
  end
41
51
 
42
52
  def add_file(file_path, archive_path = nil)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.3.48"
4
+ VERSION = "0.3.50"
5
5
  end
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.48
4
+ version: 0.3.50
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -327,9 +327,12 @@ files:
327
327
  - lib/omnizip/archive_handlers.rb
328
328
  - lib/omnizip/archive_handlers/cpio_handler.rb
329
329
  - lib/omnizip/archive_handlers/iso_handler.rb
330
+ - lib/omnizip/archive_handlers/ole_handler.rb
330
331
  - lib/omnizip/archive_handlers/rar_handler.rb
332
+ - lib/omnizip/archive_handlers/rpm_handler.rb
331
333
  - lib/omnizip/archive_handlers/seven_zip_handler.rb
332
334
  - lib/omnizip/archive_handlers/tar_handler.rb
335
+ - lib/omnizip/archive_handlers/xar_handler.rb
333
336
  - lib/omnizip/archive_handlers/zip_handler.rb
334
337
  - lib/omnizip/buffer.rb
335
338
  - lib/omnizip/buffer/memory_archive.rb
@@ -368,6 +371,7 @@ files:
368
371
  - lib/omnizip/converter.rb
369
372
  - lib/omnizip/converter/conversion_registry.rb
370
373
  - lib/omnizip/converter/conversion_strategy.rb
374
+ - lib/omnizip/converter/extract_repack_strategy.rb
371
375
  - lib/omnizip/converter/seven_zip_to_zip_strategy.rb
372
376
  - lib/omnizip/converter/zip_to_seven_zip_strategy.rb
373
377
  - lib/omnizip/crypto.rb