Class: Cabriolet::OAB::Decompressor

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

Overview

Decompressor for OAB (Outlook Offline Address Book) files

OAB files use LZX compression and come in two formats:

  • Full files (version 3.1): Complete address book data

  • Incremental patches (version 3.2): Binary patches applied to base file

This implementation is based on libmspack’s oabd.c

NOTE: This implementation cannot be fully validated due to lack of test fixtures. OAB files are specialized Outlook data files. Testing relies on round-trip compression/decompression.

Constant Summary collapse

DEFAULT_BUFFER_SIZE =

Default buffer size for I/O operations

4096

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io_system = nil, algorithm_factory = nil) ⇒ Decompressor

Initialize OAB decompressor

Parameters:

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

    I/O system or nil for default

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

    Custom algorithm factory or nil for default



29
30
31
32
33
# File 'lib/cabriolet/oab/decompressor.rb', line 29

def initialize(io_system = nil, algorithm_factory = nil)
  @io_system = io_system || System::IOSystem.new
  @algorithm_factory = algorithm_factory || Cabriolet.algorithm_factory
  @buffer_size = DEFAULT_BUFFER_SIZE
end

Instance Attribute Details

#buffer_sizeObject

Returns the value of attribute buffer_size.



20
21
22
# File 'lib/cabriolet/oab/decompressor.rb', line 20

def buffer_size
  @buffer_size
end

#io_systemObject (readonly)

Returns the value of attribute io_system.



19
20
21
# File 'lib/cabriolet/oab/decompressor.rb', line 19

def io_system
  @io_system
end

Instance Method Details

#decompress(input_file, output_file) ⇒ Integer

Decompress a full OAB file

Parameters:

  • input_file (String)

    Compressed OAB file path

  • output_file (String)

    Decompressed output path

Returns:

  • (Integer)

    Bytes written

Raises:

  • (Error)

    if decompression fails



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
# File 'lib/cabriolet/oab/decompressor.rb', line 41

def decompress(input_file, output_file)
  input_handle = @io_system.open(input_file, Constants::MODE_READ)
  output_handle = @io_system.open(output_file, Constants::MODE_WRITE)

  begin
    # Read and validate header
    header_data = @io_system.read(input_handle, 16)
    raise Error, "Failed to read OAB header" if header_data.length < 16

    header = Binary::OABStructures::FullHeader.read(header_data)
    raise Error, "Invalid OAB header" unless header.valid?

    block_max = header.block_max
    target_size = header.target_size
    total_written = 0

    # Process blocks until target size reached
    while target_size.positive?
      total_written += decompress_block(
        input_handle, output_handle, block_max, target_size
      )
      target_size -= [block_max, target_size].min
    end

    total_written
  ensure
    @io_system.close(input_handle) if input_handle
    @io_system.close(output_handle) if output_handle
  end
end

#decompress_incremental(patch_file, base_file, output_file) ⇒ Integer

Decompress an incremental patch file

Parameters:

  • patch_file (String)

    Compressed patch file path

  • base_file (String)

    Base (uncompressed) file path

  • output_file (String)

    Output file path

Returns:

  • (Integer)

    Bytes written

Raises:

  • (Error)

    if decompression fails



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/cabriolet/oab/decompressor.rb', line 79

def decompress_incremental(patch_file, base_file, output_file)
  patch_handle = @io_system.open(patch_file, Constants::MODE_READ)
  base_handle = @io_system.open(base_file, Constants::MODE_READ)
  output_handle = @io_system.open(output_file, Constants::MODE_WRITE)

  begin
    # Read and validate patch header
    header_data = @io_system.read(patch_handle, 28)
    if header_data.length < 28
      raise Error,
            "Failed to read OAB patch header"
    end

    header = Binary::OABStructures::PatchHeader.read(header_data)
    raise Error, "Invalid OAB patch header" unless header.valid?

    block_max = [header.block_max, 16].max # At least 16 for header
    target_size = header.target_size
    total_written = 0

    # Process patch blocks until target size reached
    while target_size.positive?
      total_written += decompress_patch_block(
        patch_handle, base_handle, output_handle, block_max, target_size
      )
      target_size = header.target_size - total_written
    end

    total_written
  ensure
    @io_system.close(patch_handle) if patch_handle
    @io_system.close(base_handle) if base_handle
    @io_system.close(output_handle) if output_handle
  end
end