Class: Cabriolet::CAB::Decompressor

Inherits:
Object
  • Object
show all
Defined in:
lib/cabriolet/cab/decompressor.rb

Overview

Decompressor is the main interface for CAB file operations

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io_system = nil) ⇒ Decompressor

Initialize a new CAB decompressor

Parameters:

  • io_system (System::IOSystem, nil) (defaults to: nil)

    Custom I/O system or nil for default



13
14
15
16
17
18
19
20
# File 'lib/cabriolet/cab/decompressor.rb', line 13

def initialize(io_system = nil)
  @io_system = io_system || System::IOSystem.new
  @parser = Parser.new(@io_system)
  @buffer_size = Cabriolet.default_buffer_size
  @fix_mszip = false
  @salvage = false
  @search_buffer_size = 32_768
end

Instance Attribute Details

#buffer_sizeObject

Returns the value of attribute buffer_size.



8
9
10
# File 'lib/cabriolet/cab/decompressor.rb', line 8

def buffer_size
  @buffer_size
end

#fix_mszipObject

Returns the value of attribute fix_mszip.



8
9
10
# File 'lib/cabriolet/cab/decompressor.rb', line 8

def fix_mszip
  @fix_mszip
end

#io_systemObject (readonly)

Returns the value of attribute io_system.



7
8
9
# File 'lib/cabriolet/cab/decompressor.rb', line 7

def io_system
  @io_system
end

#parserObject (readonly)

Returns the value of attribute parser.



7
8
9
# File 'lib/cabriolet/cab/decompressor.rb', line 7

def parser
  @parser
end

#salvageObject

Returns the value of attribute salvage.



8
9
10
# File 'lib/cabriolet/cab/decompressor.rb', line 8

def salvage
  @salvage
end

#search_buffer_sizeObject

Returns the value of attribute search_buffer_size.



8
9
10
# File 'lib/cabriolet/cab/decompressor.rb', line 8

def search_buffer_size
  @search_buffer_size
end

Instance Method Details

#append(cabinet, next_cabinet) ⇒ Boolean

Append a cabinet to another, merging their folders and files

Parameters:

Returns:

  • (Boolean)

    true if successful

Raises:



86
87
88
# File 'lib/cabriolet/cab/decompressor.rb', line 86

def append(cabinet, next_cabinet)
  merge_cabinets(cabinet, next_cabinet)
end

#create_decompressor(folder, input, output) ⇒ Decompressors::Base

Create appropriate decompressor for a folder

Parameters:

Returns:



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/cabriolet/cab/decompressor.rb', line 59

def create_decompressor(folder, input, output)
  case folder.compression_method
  when Constants::COMP_TYPE_NONE
    Decompressors::None.new(@io_system, input, output, @buffer_size)
  when Constants::COMP_TYPE_MSZIP
    Decompressors::MSZIP.new(@io_system, input, output, @buffer_size,
                             fix_mszip: @fix_mszip)
  when Constants::COMP_TYPE_LZX
    window_bits = folder.compression_level
    Decompressors::LZX.new(@io_system, input, output, @buffer_size,
                           window_bits: window_bits)
  when Constants::COMP_TYPE_QUANTUM
    window_bits = folder.compression_level
    Decompressors::Quantum.new(@io_system, input, output, @buffer_size,
                               window_bits: window_bits)
  else
    raise UnsupportedFormatError,
          "Unsupported compression type: #{folder.compression_method}"
  end
end

#extract_all(cabinet, output_dir, **options) ⇒ Integer

Extract all files from the cabinet

Parameters:

  • cabinet (Models::Cabinet)

    Cabinet to extract from

  • output_dir (String)

    Directory to extract to

  • options (Hash)

    Extraction options

Returns:

  • (Integer)

    Number of files extracted



48
49
50
51
# File 'lib/cabriolet/cab/decompressor.rb', line 48

def extract_all(cabinet, output_dir, **options)
  extractor = Extractor.new(@io_system, self)
  extractor.extract_all(cabinet, output_dir, **options)
end

#extract_file(file, output_path, **options) ⇒ Integer

Extract a single file from the cabinet

Parameters:

  • file (Models::File)

    File to extract

  • output_path (String)

    Where to write the file

  • options (Hash)

    Extraction options

Returns:

  • (Integer)

    Number of bytes extracted



37
38
39
40
# File 'lib/cabriolet/cab/decompressor.rb', line 37

def extract_file(file, output_path, **options)
  extractor = Extractor.new(@io_system, self)
  extractor.extract_file(file, output_path, **options)
end

#open(filename) ⇒ Models::Cabinet

Open and parse a CAB file

Parameters:

  • filename (String)

    Path to the CAB file

Returns:

Raises:



27
28
29
# File 'lib/cabriolet/cab/decompressor.rb', line 27

def open(filename)
  @parser.parse(filename)
end

#prepend(cabinet, prev_cabinet) ⇒ Boolean

Prepend a cabinet to another, merging their folders and files

Parameters:

Returns:

  • (Boolean)

    true if successful

Raises:



96
97
98
# File 'lib/cabriolet/cab/decompressor.rb', line 96

def prepend(cabinet, prev_cabinet)
  merge_cabinets(prev_cabinet, cabinet)
end

#search(filename) ⇒ Models::Cabinet?

Search for embedded CAB files within a file

Parameters:

  • filename (String)

    Path to file to search

Returns:



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/cabriolet/cab/decompressor.rb', line 104

def search(filename)
  search_buf = Array.new(@search_buffer_size)
  first_cabinet = nil
  link_cabinet = nil
  first_len = 0
  false_cabs = 0

  handle = @io_system.open(filename, Constants::MODE_READ)
  file_length = handle.size

  # Check for InstallShield header at start of file
  if file_length >= 4
    header = @io_system.read(handle, 4)
    @io_system.seek(handle, 0, Constants::SEEK_START)
    if header.unpack1("V") == 0x28635349
      @io_system.message(handle, "WARNING; found InstallShield header. Use unshield " \
                                 "(https://github.com/twogood/unshield) to unpack this file")
    end
  end

  offset = 0
  while offset < file_length
    # Calculate read length
    length = [file_length - offset, @search_buffer_size].min

    # Read chunk
    @io_system.seek(handle, offset, Constants::SEEK_START)
    bytes_read = @io_system.read(handle, length)
    break if bytes_read.nil? || bytes_read.empty?

    search_buf[0, bytes_read.bytesize] = bytes_read.bytes

    # Search for cabinets in this chunk
    cab_offset = find_cabinet_in_buffer(search_buf, bytes_read.size,
                                        offset, handle, filename, file_length)

    if cab_offset
      # Try to parse cabinet at this offset
      cabinet = try_parse_cab_at_offset(handle, filename, cab_offset)

      if cabinet
        # Capture first cabinet length
        first_len = cabinet.length if cab_offset.zero?

        # Link into list
        if first_cabinet.nil?
          first_cabinet = cabinet
        else
          link_cabinet.next = cabinet
        end
        link_cabinet = cabinet

        # Continue searching after this cabinet
        offset = cab_offset + cabinet.length
      else
        false_cabs += 1
        # Restart search after signature
        offset = cab_offset + 4
      end
    else
      # No cabinet found in this chunk, move to next
      offset += length
    end
  end

  @io_system.close(handle)

  # Warn about truncated/extra data
  if first_len.positive? && first_len != file_length && (first_cabinet.nil? || first_cabinet.base_offset.zero?)
    if first_len < file_length
      @io_system.message(handle,
                         "WARNING; possible #{file_length - first_len} extra bytes at end of file.")
    else
      @io_system.message(handle,
                         "WARNING; file possibly truncated by #{first_len - file_length} bytes.")
    end
  end

  if false_cabs.positive? && Cabriolet.verbose
    @io_system.message(handle,
                       "#{false_cabs} false cabinets found")
  end

  first_cabinet
rescue StandardError
  @io_system.close(handle) if handle
  raise
end