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, algorithm_factory = nil) ⇒ Decompressor

Initialize a new CAB decompressor

Parameters:

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

    Custom I/O system or nil for default

  • algorithm_factory (AlgorithmFactory, nil) (defaults to: nil)

    Custom algorithm factory or nil for default



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

def initialize(io_system = nil, algorithm_factory = nil)
  @io_system = io_system || System::IOSystem.new
  @algorithm_factory = algorithm_factory || Cabriolet.algorithm_factory
  @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:



81
82
83
# File 'lib/cabriolet/cab/decompressor.rb', line 81

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:



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/cabriolet/cab/decompressor.rb', line 61

def create_decompressor(folder, input, output)
  @algorithm_factory.create(
    folder.compression_method,
    :decompressor,
    @io_system,
    input,
    output,
    @buffer_size,
    fix_mszip: @fix_mszip,
    salvage: @salvage,
    window_bits: folder.compression_level,
  )
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



50
51
52
53
# File 'lib/cabriolet/cab/decompressor.rb', line 50

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



39
40
41
42
# File 'lib/cabriolet/cab/decompressor.rb', line 39

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:



29
30
31
# File 'lib/cabriolet/cab/decompressor.rb', line 29

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:



91
92
93
# File 'lib/cabriolet/cab/decompressor.rb', line 91

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:



99
100
101
102
103
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
# File 'lib/cabriolet/cab/decompressor.rb', line 99

def search(filename)
  # Reuse search buffer across searches for better performance
  search_buf = @search_buffer ||= 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