Module: Omnizip::Formats::SevenZip

Defined in:
lib/omnizip/formats/seven_zip.rb,
lib/omnizip/formats/seven_zip/header.rb,
lib/omnizip/formats/seven_zip/models.rb,
lib/omnizip/formats/seven_zip/parser.rb,
lib/omnizip/formats/seven_zip/reader.rb,
lib/omnizip/formats/seven_zip/writer.rb,
lib/omnizip/formats/seven_zip/constants.rb,
lib/omnizip/formats/seven_zip/coder_chain.rb,
lib/omnizip/formats/seven_zip/header_writer.rb,
lib/omnizip/formats/seven_zip/models/folder.rb,
lib/omnizip/formats/seven_zip/encoded_header.rb,
lib/omnizip/formats/seven_zip/file_collector.rb,
lib/omnizip/formats/seven_zip/encrypted_header.rb,
lib/omnizip/formats/seven_zip/header_encryptor.rb,
lib/omnizip/formats/seven_zip/models/coder_info.rb,
lib/omnizip/formats/seven_zip/models/file_entry.rb,
lib/omnizip/formats/seven_zip/stream_compressor.rb,
lib/omnizip/formats/seven_zip/models/stream_info.rb,
lib/omnizip/formats/seven_zip/stream_decompressor.rb,
lib/omnizip/formats/seven_zip/split_archive_reader.rb,
lib/omnizip/formats/seven_zip/split_archive_writer.rb,
lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb

Overview

.7z archive format support Provides read and write access to 7-Zip archives

This module implements the .7z archive format specification, supporting:

  • Format signature and header validation
  • Archive structure parsing
  • File extraction
  • Archive creation
  • Split archives (multi-volume)

Defined Under Namespace

Modules: Constants, EncodedHeader, Models Classes: Bcj2StreamDecompressor, CoderChain, EncryptedHeader, FileCollector, Header, HeaderEncryptor, HeaderWriter, Parser, Reader, SplitArchiveReader, SplitArchiveWriter, StreamCompressor, StreamDecompressor, Writer

Class Method Summary collapse

Class Method Details

.create(path, options = {}) {|writer| ... } ⇒ Object

Create a new .7z archive

Parameters:

  • path (String)

    Output path

  • options (Hash) (defaults to: {})

    Compression options

Options Hash (options):

  • :volume_size (Integer)

    Volume size for split archives

  • :password (String)

    Password for header encryption

  • :encrypt_headers (Boolean)

    Encrypt archive headers

Yields:

  • (writer)

    Block for adding files

Yield Parameters:

  • writer (Writer)

    Archive writer



64
65
66
67
68
69
# File 'lib/omnizip/formats/seven_zip.rb', line 64

def self.create(path, options = {})
  writer = Writer.new(path, options)
  yield writer if block_given?
  writer.write
  writer
end

.create_split(path, split_options, options = {}) {|writer| ... } ⇒ Object

Create a split .7z archive

Parameters:

  • path (String)

    Base path (e.g., "backup.7z.001")

  • split_options (Models::SplitOptions)

    Split configuration

  • options (Hash) (defaults to: {})

    Compression options

Yields:

  • (writer)

    Block for adding files

Yield Parameters:



78
79
80
81
82
83
# File 'lib/omnizip/formats/seven_zip.rb', line 78

def self.create_split(path, split_options, options = {})
  writer = SplitArchiveWriter.new(path, options, split_options)
  yield writer if block_given?
  writer.write
  writer
end

.open(path, options = {}) {|reader| ... } ⇒ Reader

Open existing .7z archive

Parameters:

  • path (String)

    Archive path

  • options (Hash) (defaults to: {})

    Reader options

Options Hash (options):

  • :password (String)

    Password for encrypted headers

Yields:

  • (reader)

    Block for reading archive

Yield Parameters:

  • reader (Reader)

    Archive reader

Returns:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/omnizip/formats/seven_zip.rb', line 93

def self.open(path, options = {})
  reader = Reader.new(path, options)
  reader.open

  if block_given?
    begin
      yield reader
    ensure
      reader.split_reader&.close
    end
  end

  reader
end

.register!Object

Auto-register .7z format when loaded



179
180
181
# File 'lib/omnizip/formats/seven_zip.rb', line 179

def self.register!
  FormatRegistry.register(".7z", "Omnizip::Formats::SevenZip::Reader")
end

.search_embedded(path) ⇒ Integer?

Search for embedded .7z archive in self-extracting executable

Scans the file for 7z signatures and validates each candidate by checking the Start Header (version bytes, CRC, and header field sanity). Returns the offset of the first valid embedded archive.

Parameters:

  • path (String)

    Path to potential self-extracting archive

Returns:

  • (Integer, nil)

    Offset of embedded 7z signature, or nil if not found



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/omnizip/formats/seven_zip.rb', line 116

def self.search_embedded(path)
  data = File.binread(path)
  file_size = data.bytesize
  signature = Constants::SIGNATURE
  pos = 0

  while (offset = data.index(signature, pos))
    if valid_7z_start_header?(data, offset, file_size)
      return offset
    end

    pos = offset + 1
  end

  nil
end

.valid_7z_start_header?(data, offset, file_size) ⇒ Boolean

Validate a candidate 7z Start Header at the given offset.

Checks:

  1. Enough bytes remain for a full 32-byte Start Header
  2. Major version is 0 and minor version is 4 (only supported version)
  3. Next Header offset + size points within (or at the end of) the file
  4. Start Header CRC matches the header content

Parameters:

  • data (String)

    File data

  • offset (Integer)

    Offset of the 7z signature

  • file_size (Integer)

    Total file size

Returns:

  • (Boolean)

    true if the header looks valid



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/omnizip/formats/seven_zip.rb', line 145

def self.valid_7z_start_header?(data, offset, file_size)
  header_size = Constants::START_HEADER_SIZE # 32
  return false if offset + header_size > file_size

  header_data = data.byteslice(offset, header_size)

  # Check version byte (offset 6) - only major version is validated per 7-Zip SDK
  # See: https://github.com/ip7z/7zip/blob/main/CPP/7zip/Archive/7z/7zIn.cpp#L1591-L1598
  major_version = header_data.getbyte(6)
  return false unless major_version == Constants::MAJOR_VERSION

  # Parse Start Header fields (bytes 12-31)
  next_header_data = header_data.byteslice(12, 20)
  next_header_offset = next_header_data.unpack1("Q<")
  next_header_size = next_header_data.byteslice(8, 8).unpack1("Q<")

  # next_header_offset is relative to end of Start Header
  header_end = offset + header_size + next_header_offset + next_header_size
  return false if header_end > file_size
  return false if next_header_size.zero? || next_header_size > file_size

  # Validate Start Header CRC (bytes 8-11) over next_header_data (bytes 12-31)
  stored_crc = header_data.byteslice(8, 4).unpack1("V")
  computed_crc = Omnizip::Checksums::Crc32.new.tap do |c|
    c.update(next_header_data)
  end.finalize
  return false unless stored_crc == computed_crc

  true
rescue StandardError
  false
end