Class: Omnizip::Formats::SevenZip::StreamDecompressor

Inherits:
Object
  • Object
show all
Defined in:
lib/omnizip/formats/seven_zip/stream_decompressor.rb

Overview

Decompresses .7z streams using coder chains Handles packed stream extraction and CRC validation

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(archive_io, folder, pack_pos, pack_size, header = nil) ⇒ StreamDecompressor

Initialize decompressor

Parameters:

  • archive_io (IO)

    Archive file handle

  • folder (Models::Folder)

    Folder specification

  • pack_pos (Integer)

    Position of packed data

  • pack_size (Integer)

    Size of packed data

  • header (Header, nil) (defaults to: nil)

    Optional header for split archive handling



20
21
22
23
24
25
26
27
# File 'lib/omnizip/formats/seven_zip/stream_decompressor.rb', line 20

def initialize(archive_io, folder, pack_pos, pack_size, header = nil)
  @archive_io = archive_io
  @folder = folder
  @pack_pos = pack_pos
  @pack_size = pack_size
  @header = header
  @chain_config = CoderChain.build_from_folder(folder)
end

Instance Attribute Details

#archive_ioObject (readonly)

Returns the value of attribute archive_io.



11
12
13
# File 'lib/omnizip/formats/seven_zip/stream_decompressor.rb', line 11

def archive_io
  @archive_io
end

#chain_configObject (readonly)

Returns the value of attribute chain_config.



11
12
13
# File 'lib/omnizip/formats/seven_zip/stream_decompressor.rb', line 11

def chain_config
  @chain_config
end

#folderObject (readonly)

Returns the value of attribute folder.



11
12
13
# File 'lib/omnizip/formats/seven_zip/stream_decompressor.rb', line 11

def folder
  @folder
end

Instance Method Details

#decompress(size) ⇒ String

Decompress stream to output

Parameters:

  • size (Integer)

    Expected uncompressed size

Returns:

  • (String)

    Decompressed data

Raises:

  • (RuntimeError)

    on decompression error



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/omnizip/formats/seven_zip/stream_decompressor.rb', line 34

def decompress(size)
  # Seek to packed data
  @archive_io.seek(@pack_pos)

  # For multi-volume archives, pack_size from stream_info may be incomplete.
  # For solid archives spanning volumes, the actual compressed data extends
  # from pack_pos to where the next header starts.
  if @archive_io.is_a?(SplitArchiveReader::MultiVolumeIO) && @header
    # next_header_offset is relative to start_pos_after_header
    # Actual packed data size is from pack_pos to next header position
    next_header_position = @header.start_pos_after_header + @header.next_header_offset
    actual_pack_size = next_header_position - @pack_pos
    packed_data = @archive_io.read(actual_pack_size)
  else
    # Regular single-file archive: use pack_size from stream info
    packed_data = @archive_io.read(@pack_size)
  end

  return packed_data if @chain_config.nil? # No compression

  # Get algorithm
  algo_sym = @chain_config[:algorithm]
  return packed_data unless algo_sym # Copy method

  # Get algorithm class
  algo_class = Omnizip::AlgorithmRegistry.get(algo_sym)
  raise "Algorithm not found: #{algo_sym}" unless algo_class

  # Decompress
  input_io = StringIO.new(packed_data)
  output_io = StringIO.new
  output_io.set_encoding("BINARY")

  # Build options from coder properties for 7-Zip format
  decoder_options = build_decoder_options

  decoder = algo_class.new(decoder_options)
  decoder.decompress(input_io, output_io, size: size)

  result = output_io.string

  # Apply filters if present
  if @chain_config[:filters] && !@chain_config[:filters].empty?
    @chain_config[:filters].reverse_each do |filter_sym|
      filter_class = Omnizip::FilterRegistry.get(filter_sym)
      next unless filter_class

      # BCJ2 requires special handling with multiple streams
      if filter_sym == :bcj2
        raise "BCJ2 archives require multi-stream decompression which is not yet implemented. " \
              "Please use the 7z command-line tool for this archive."
      end

      filter = filter_class.new
      filtered = StringIO.new
      filter.reverse(StringIO.new(result), filtered)
      result = filtered.string
    end
  end

  result
end

#decompress_and_verify(size, expected_crc = nil) ⇒ String

Decompress and verify CRC

Parameters:

  • size (Integer)

    Expected uncompressed size

  • expected_crc (Integer, nil) (defaults to: nil)

    Expected CRC32 value

Returns:

  • (String)

    Decompressed data

Raises:

  • (RuntimeError)

    if CRC mismatch



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/omnizip/formats/seven_zip/stream_decompressor.rb', line 103

def decompress_and_verify(size, expected_crc = nil)
  data = decompress(size)

  if expected_crc
    crc = Omnizip::Checksums::Crc32.new
    crc.update(data)
    actual_crc = crc.value

    unless actual_crc == expected_crc
      raise "CRC mismatch: expected 0x#{expected_crc.to_s(16)}, " \
            "got 0x#{actual_crc.to_s(16)}"
    end
  end

  data
end