Task 075: .CAB File Format

Task 075: .CAB File Format

1. List of Properties of the .CAB File Format Intrinsic to Its File System

The Microsoft Cabinet (.CAB) file format is a compressed archive format designed for efficient storage and distribution of files. Its structure includes a header, folders, file entries, and data blocks. The intrinsic properties refer to the metadata fields within the file's structure that define its organization, compression, and linkage across multiple cabinets if applicable. Below is a comprehensive list of these properties, derived from the official specification, grouped by structural component for clarity.

CFHEADER (Cabinet Header) Properties

  • Signature: A 4-byte identifier ("MSCF" or bytes 0x4D, 0x53, 0x43, 0x46) confirming the file type.
  • Reserved1: A 4-byte reserved field, always set to 0.
  • cbCabinet: A 4-byte value indicating the total size of the cabinet file in bytes.
  • Reserved2: A 4-byte reserved field, always set to 0.
  • coffFiles: A 4-byte offset to the first CFFILE entry.
  • VersionMinor: A 1-byte minor version of the cabinet format (typically 3).
  • VersionMajor: A 1-byte major version of the cabinet format (typically 1).
  • cFolders: A 2-byte count of CFFOLDER entries in the cabinet.
  • cFiles: A 2-byte count of CFFILE entries in the cabinet.
  • Flags: A 2-byte bitmapped field indicating optional features:
  • Bit 0 (cfhdrPREV_CABINET): Presence of previous cabinet information.
  • Bit 1 (cfhdrNEXT_CABINET): Presence of next cabinet information.
  • Bit 2 (cfhdrRESERVE_PRESENT): Presence of reserved fields.
  • setID: A 2-byte identifier binding linked cabinets (consistent across a set).
  • iCabinet: A 2-byte sequential number of this cabinet in a multi-cabinet set (starting from 0).
  • cbCFHeader (optional): A 2-byte size of the reserved area in the header (if cfhdrRESERVE_PRESENT is set; 0-60000 bytes).
  • cbCFFolder (optional): A 1-byte size of reserved area per folder (if cfhdrRESERVE_PRESENT is set; 0-255 bytes).
  • cbCFData (optional): A 1-byte size of reserved area per data block (if cfhdrRESERVE_PRESENT is set; 0-255 bytes).
  • abReserve (optional): Variable-length reserved data in the header (size given by cbCFHeader).
  • szCabinetPrev (optional): NULL-terminated string (up to 256 bytes) naming the previous cabinet (if cfhdrPREV_CABINET is set).
  • szDiskPrev (optional): NULL-terminated string (up to 256 bytes) describing the disk of the previous cabinet (if cfhdrPREV_CABINET is set).
  • szCabinetNext (optional): NULL-terminated string (up to 256 bytes) naming the next cabinet (if cfhdrNEXT_CABINET is set).
  • szDiskNext (optional): NULL-terminated string (up to 256 bytes) describing the disk of the next cabinet (if cfhdrNEXT_CABINET is set).

CFFOLDER (Folder) Properties (One per Folder)

  • coffCabStart: A 4-byte offset to the first CFDATA block for this folder.
  • cCFData: A 2-byte count of CFDATA blocks in this cabinet for the folder.
  • typeCompress: A 2-byte compression type:
  • 0x0000: No compression.
  • 0x0001: MSZIP.
  • 0x0002: Quantum.
  • 0x0003: LZX.
  • abReserve (optional): Variable-length reserved data per folder (size given by cbCFFolder).

CFFILE (File Entry) Properties (One per File)

  • cbFile: A 4-byte uncompressed size of the file in bytes.
  • uoffFolderStart: A 4-byte uncompressed offset of the file within its folder.
  • iFolder: A 2-byte index of the containing folder.
  • date: A 2-byte MS-DOS date stamp.
  • time: A 2-byte MS-DOS time stamp.
  • attribs: A 2-byte file attributes (e.g., read-only, hidden).
  • szName: NULL-terminated string specifying the file name (variable length).

CFDATA (Data Block) Properties (One per Data Block)

  • csum: A 4-byte checksum of the data block.
  • cbData: A 2-byte count of compressed bytes in the block.
  • cbUncomp: A 2-byte count of uncompressed bytes in the block.
  • abReserve (optional): Variable-length reserved data per block (size given by cbCFData).
  • ab: Variable-length compressed data (size given by cbData).

These properties collectively define the file system's structure, enabling parsing, extraction, and reconstruction of the archive.

(Note: The second link directs to a page hosting the file for download; ensure compliance with terms of use when accessing.)

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .CAB File Dump

The following is a self-contained HTML page with embedded JavaScript that allows users to drag and drop a .CAB file. Upon dropping, it parses the file and displays all properties listed in section 1 on the screen. It uses the File API and DataView for binary parsing.

CAB File Property Dumper

Drag and Drop .CAB File

Drop .CAB file here

4. Python Class for .CAB File Handling

The following Python class can open a .CAB file, decode its structure, read and print all properties, and write a new .CAB file with the same properties (though writing is basic and assumes no compression modifications for simplicity).

import struct
import os

class CabFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.data = None
        self.properties = {}
        self.folders = []
        self.files = []
        self.data_blocks = []

    def read(self):
        with open(self.filepath, 'rb') as f:
            self.data = f.read()
        self.decode()

    def decode(self):
        offset = 0
        # CFHEADER
        self.properties['signature'] = self.data[offset:offset+4].decode('ascii')
        offset += 4
        self.properties['reserved1'] = struct.unpack_from('<I', self.data, offset)[0]
        offset += 4
        self.properties['cbCabinet'] = struct.unpack_from('<I', self.data, offset)[0]
        offset += 4
        self.properties['reserved2'] = struct.unpack_from('<I', self.data, offset)[0]
        offset += 4
        self.properties['coffFiles'] = struct.unpack_from('<I', self.data, offset)[0]
        offset += 4
        self.properties['versionMinor'] = self.data[offset]
        offset += 1
        self.properties['versionMajor'] = self.data[offset]
        offset += 1
        self.properties['cFolders'] = struct.unpack_from('<H', self.data, offset)[0]
        cFolders = self.properties['cFolders']
        offset += 2
        self.properties['cFiles'] = struct.unpack_from('<H', self.data, offset)[0]
        cFiles = self.properties['cFiles']
        offset += 2
        self.properties['flags'] = struct.unpack_from('<H', self.data, offset)[0]
        flags = self.properties['flags']
        offset += 2
        self.properties['setID'] = struct.unpack_from('<H', self.data, offset)[0]
        offset += 2
        self.properties['iCabinet'] = struct.unpack_from('<H', self.data, offset)[0]
        offset += 2

        cbCFHeader = cbCFFolder = cbCFData = 0
        if flags & 0x0004:
            cbCFHeader = struct.unpack_from('<H', self.data, offset)[0]
            self.properties['cbCFHeader'] = cbCFHeader
            offset += 2
            cbCFFolder = self.data[offset]
            self.properties['cbCFFolder'] = cbCFFolder
            offset += 1
            cbCFData = self.data[offset]
            self.properties['cbCFData'] = cbCFData
            offset += 1

        if cbCFHeader > 0:
            self.properties['abReserve_header'] = self.data[offset:offset+cbCFHeader]
            offset += cbCFHeader

        if flags & 0x0001:
            szCabinetPrev = self._read_null_term(offset)
            self.properties['szCabinetPrev'] = szCabinetPrev
            offset += len(szCabinetPrev) + 1
            szDiskPrev = self._read_null_term(offset)
            self.properties['szDiskPrev'] = szDiskPrev
            offset += len(szDiskPrev) + 1

        if flags & 0x0002:
            szCabinetNext = self._read_null_term(offset)
            self.properties['szCabinetNext'] = szCabinetNext
            offset += len(szCabinetNext) + 1
            szDiskNext = self._read_null_term(offset)
            self.properties['szDiskNext'] = szDiskNext
            offset += len(szDiskNext) + 1

        # CFFOLDERs
        for i in range(cFolders):
            folder = {}
            folder['coffCabStart'] = struct.unpack_from('<I', self.data, offset)[0]
            offset += 4
            folder['cCFData'] = struct.unpack_from('<H', self.data, offset)[0]
            offset += 2
            folder['typeCompress'] = struct.unpack_from('<H', self.data, offset)[0]
            offset += 2
            if cbCFFolder > 0:
                folder['abReserve_folder'] = self.data[offset:offset+cbCFFolder]
                offset += cbCFFolder
            self.folders.append(folder)

        # CFFILEs
        offset = self.properties['coffFiles']
        for i in range(cFiles):
            file_entry = {}
            file_entry['cbFile'] = struct.unpack_from('<I', self.data, offset)[0]
            offset += 4
            file_entry['uoffFolderStart'] = struct.unpack_from('<I', self.data, offset)[0]
            offset += 4
            file_entry['iFolder'] = struct.unpack_from('<H', self.data, offset)[0]
            offset += 2
            file_entry['date'] = struct.unpack_from('<H', self.data, offset)[0]
            offset += 2
            file_entry['time'] = struct.unpack_from('<H', self.data, offset)[0]
            offset += 2
            file_entry['attribs'] = struct.unpack_from('<H', self.data, offset)[0]
            offset += 2
            szName = self._read_null_term(offset)
            file_entry['szName'] = szName
            offset += len(szName) + 1
            self.files.append(file_entry)

        # CFDATA blocks
        for folder in self.folders:
            offset = folder['coffCabStart']
            for d in range(folder['cCFData']):
                data_block = {}
                data_block['csum'] = struct.unpack_from('<I', self.data, offset)[0]
                offset += 4
                data_block['cbData'] = struct.unpack_from('<H', self.data, offset)[0]
                cbData = data_block['cbData']
                offset += 2
                data_block['cbUncomp'] = struct.unpack_from('<H', self.data, offset)[0]
                offset += 2
                if cbCFData > 0:
                    data_block['abReserve_data'] = self.data[offset:offset+cbCFData]
                    offset += cbCFData
                data_block['ab'] = self.data[offset:offset+cbData]
                offset += cbData
                self.data_blocks.append(data_block)

    def _read_null_term(self, offset):
        end = offset
        while self.data[end] != 0:
            end += 1
        return self.data[offset:end].decode('ascii')

    def print_properties(self):
        print("CFHEADER Properties:")
        for key, value in self.properties.items():
            print(f"  {key}: {value}")
        print("\nFolders:")
        for i, folder in enumerate(self.folders):
            print(f"  Folder {i}:")
            for key, value in folder.items():
                print(f"    {key}: {value}")
        print("\nFiles:")
        for i, file_entry in enumerate(self.files):
            print(f"  File {i}:")
            for key, value in file_entry.items():
                print(f"    {key}: {value}")
        print("\nData Blocks:")
        for i, data_block in enumerate(self.data_blocks):
            print(f"  Data Block {i}:")
            for key, value in data_block.items():
                if key in ['abReserve_data', 'ab']:
                    print(f"    {key}: [binary data, size {len(value)}]")
                else:
                    print(f"    {key}: {value}")

    def write(self, output_path):
        # Basic write: reconstruct the data as is (no modifications)
        with open(output_path, 'wb') as f:
            f.write(self.data)
        print(f"Written to {output_path}")

# Example usage:
# cab = CabFile('sample.cab')
# cab.read()
# cab.print_properties()
# cab.write('output.cab')

5. Java Class for .CAB File Handling

The following Java class performs similar operations: opening, decoding, reading, printing properties, and writing the file.

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.util.*;

public class CabFile {
    private Path filepath;
    private ByteBuffer buffer;
    private Map<String, Object> properties = new HashMap<>();
    private List<Map<String, Object>> folders = new ArrayList<>();
    private List<Map<String, Object>> files = new ArrayList<>();
    private List<Map<String, Object>> dataBlocks = new ArrayList<>();

    public CabFile(String filepath) {
        this.filepath = Paths.get(filepath);
    }

    public void read() throws IOException {
        try (FileChannel channel = FileChannel.open(filepath, StandardOpenOption.READ)) {
            buffer = ByteBuffer.allocate((int) channel.size());
            channel.read(buffer);
            buffer.flip();
            buffer.order(ByteOrder.LITTLE_ENDIAN);
        }
        decode();
    }

    private void decode() {
        int offset = 0;
        // CFHEADER
        byte[] sigBytes = new byte[4];
        buffer.position(offset);
        buffer.get(sigBytes);
        properties.put("signature", new String(sigBytes));
        offset += 4;
        properties.put("reserved1", buffer.getInt(offset));
        offset += 4;
        properties.put("cbCabinet", buffer.getInt(offset));
        offset += 4;
        properties.put("reserved2", buffer.getInt(offset));
        offset += 4;
        properties.put("coffFiles", buffer.getInt(offset));
        offset += 4;
        properties.put("versionMinor", (int) buffer.get(offset));
        offset += 1;
        properties.put("versionMajor", (int) buffer.get(offset));
        offset += 1;
        properties.put("cFolders", (int) buffer.getShort(offset));
        int cFolders = (int) properties.get("cFolders");
        offset += 2;
        properties.put("cFiles", (int) buffer.getShort(offset));
        int cFiles = (int) properties.get("cFiles");
        offset += 2;
        properties.put("flags", (int) buffer.getShort(offset));
        int flags = (int) properties.get("flags");
        offset += 2;
        properties.put("setID", (int) buffer.getShort(offset));
        offset += 2;
        properties.put("iCabinet", (int) buffer.getShort(offset));
        offset += 2;

        int cbCFHeader = 0, cbCFFolder = 0, cbCFData = 0;
        if ((flags & 0x0004) != 0) {
            cbCFHeader = buffer.getShort(offset) & 0xFFFF;
            properties.put("cbCFHeader", cbCFHeader);
            offset += 2;
            cbCFFolder = buffer.get(offset) & 0xFF;
            properties.put("cbCFFolder", cbCFFolder);
            offset += 1;
            cbCFData = buffer.get(offset) & 0xFF;
            properties.put("cbCFData", cbCFData);
            offset += 1;
        }

        if (cbCFHeader > 0) {
            byte[] abReserve = new byte[cbCFHeader];
            buffer.position(offset);
            buffer.get(abReserve);
            properties.put("abReserve_header", abReserve);
            offset += cbCFHeader;
        }

        if ((flags & 0x0001) != 0) {
            String szCabinetPrev = readNullTermString(offset);
            properties.put("szCabinetPrev", szCabinetPrev);
            offset += szCabinetPrev.length() + 1;
            String szDiskPrev = readNullTermString(offset);
            properties.put("szDiskPrev", szDiskPrev);
            offset += szDiskPrev.length() + 1;
        }

        if ((flags & 0x0002) != 0) {
            String szCabinetNext = readNullTermString(offset);
            properties.put("szCabinetNext", szCabinetNext);
            offset += szCabinetNext.length() + 1;
            String szDiskNext = readNullTermString(offset);
            properties.put("szDiskNext", szDiskNext);
            offset += szDiskNext.length() + 1;
        }

        // CFFOLDERs
        for (int i = 0; i < cFolders; i++) {
            Map<String, Object> folder = new HashMap<>();
            folder.put("coffCabStart", buffer.getInt(offset));
            offset += 4;
            folder.put("cCFData", (int) buffer.getShort(offset));
            offset += 2;
            folder.put("typeCompress", (int) buffer.getShort(offset));
            offset += 2;
            if (cbCFFolder > 0) {
                byte[] abReserve = new byte[cbCFFolder];
                buffer.position(offset);
                buffer.get(abReserve);
                folder.put("abReserve_folder", abReserve);
                offset += cbCFFolder;
            }
            folders.add(folder);
        }

        // CFFILEs
        offset = (int) properties.get("coffFiles");
        for (int i = 0; i < cFiles; i++) {
            Map<String, Object> fileEntry = new HashMap<>();
            fileEntry.put("cbFile", buffer.getInt(offset));
            offset += 4;
            fileEntry.put("uoffFolderStart", buffer.getInt(offset));
            offset += 4;
            fileEntry.put("iFolder", (int) buffer.getShort(offset));
            offset += 2;
            fileEntry.put("date", (int) buffer.getShort(offset));
            offset += 2;
            fileEntry.put("time", (int) buffer.getShort(offset));
            offset += 2;
            fileEntry.put("attribs", (int) buffer.getShort(offset));
            offset += 2;
            String szName = readNullTermString(offset);
            fileEntry.put("szName", szName);
            offset += szName.length() + 1;
            files.add(fileEntry);
        }

        // CFDATA blocks
        for (Map<String, Object> folder : folders) {
            offset = (int) folder.get("coffCabStart");
            int cCFData = (int) folder.get("cCFData");
            for (int d = 0; d < cCFData; d++) {
                Map<String, Object> dataBlock = new HashMap<>();
                dataBlock.put("csum", buffer.getInt(offset));
                offset += 4;
                int cbData = buffer.getShort(offset) & 0xFFFF;
                dataBlock.put("cbData", cbData);
                offset += 2;
                dataBlock.put("cbUncomp", (int) buffer.getShort(offset));
                offset += 2;
                if (cbCFData > 0) {
                    byte[] abReserve = new byte[cbCFData];
                    buffer.position(offset);
                    buffer.get(abReserve);
                    dataBlock.put("abReserve_data", abReserve);
                    offset += cbCFData;
                }
                byte[] ab = new byte[cbData];
                buffer.position(offset);
                buffer.get(ab);
                dataBlock.put("ab", ab);
                offset += cbData;
                dataBlocks.add(dataBlock);
            }
        }
    }

    private String readNullTermString(int offset) {
        buffer.position(offset);
        StringBuilder sb = new StringBuilder();
        byte b;
        while ((b = buffer.get()) != 0) {
            sb.append((char) b);
        }
        return sb.toString();
    }

    public void printProperties() {
        System.out.println("CFHEADER Properties:");
        properties.forEach((key, value) -> {
            if (value instanceof byte[]) {
                System.out.println("  " + key + ": [binary data, size " + ((byte[]) value).length + "]");
            } else {
                System.out.println("  " + key + ": " + value);
            }
        });
        System.out.println("\nFolders:");
        for (int i = 0; i < folders.size(); i++) {
            System.out.println("  Folder " + i + ":");
            folders.get(i).forEach((key, value) -> {
                if (value instanceof byte[]) {
                    System.out.println("    " + key + ": [binary data, size " + ((byte[]) value).length + "]");
                } else {
                    System.out.println("    " + key + ": " + value);
                }
            });
        }
        System.out.println("\nFiles:");
        for (int i = 0; i < files.size(); i++) {
            System.out.println("  File " + i + ":");
            files.get(i).forEach((key, value) -> System.out.println("    " + key + ": " + value));
        }
        System.out.println("\nData Blocks:");
        for (int i = 0; i < dataBlocks.size(); i++) {
            System.out.println("  Data Block " + i + ":");
            dataBlocks.get(i).forEach((key, value) -> {
                if (value instanceof byte[]) {
                    System.out.println("    " + key + ": [binary data, size " + ((byte[]) value).length + "]");
                } else {
                    System.out.println("    " + key + ": " + value);
                }
            });
        }
    }

    public void write(String outputPath) throws IOException {
        // Basic write: copy the original data
        Files.copy(filepath, Paths.get(outputPath), StandardCopyOption.REPLACE_EXISTING);
        System.out.println("Written to " + outputPath);
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     CabFile cab = new CabFile("sample.cab");
    //     cab.read();
    //     cab.printProperties();
    //     cab.write("output.cab");
    // }
}

6. JavaScript Class for .CAB File Handling

The following JavaScript class uses Node.js for file I/O. It opens, decodes, reads, prints properties to console, and writes the file.

const fs = require('fs');

class CabFile {
    constructor(filepath) {
        this.filepath = filepath;
        this.data = null;
        this.properties = {};
        this.folders = [];
        this.files = [];
        this.dataBlocks = [];
    }

    read() {
        this.data = fs.readFileSync(this.filepath);
        this.decode();
    }

    decode() {
        const dv = new DataView(this.data.buffer);
        let offset = 0;
        // CFHEADER
        this.properties.signature = String.fromCharCode(dv.getUint8(offset), dv.getUint8(offset+1), dv.getUint8(offset+2), dv.getUint8(offset+3));
        offset += 4;
        this.properties.reserved1 = dv.getUint32(offset, true);
        offset += 4;
        this.properties.cbCabinet = dv.getUint32(offset, true);
        offset += 4;
        this.properties.reserved2 = dv.getUint32(offset, true);
        offset += 4;
        this.properties.coffFiles = dv.getUint32(offset, true);
        offset += 4;
        this.properties.versionMinor = dv.getUint8(offset);
        offset += 1;
        this.properties.versionMajor = dv.getUint8(offset);
        offset += 1;
        this.properties.cFolders = dv.getUint16(offset, true);
        const cFolders = this.properties.cFolders;
        offset += 2;
        this.properties.cFiles = dv.getUint16(offset, true);
        const cFiles = this.properties.cFiles;
        offset += 2;
        this.properties.flags = dv.getUint16(offset, true);
        const flags = this.properties.flags;
        offset += 2;
        this.properties.setID = dv.getUint16(offset, true);
        offset += 2;
        this.properties.iCabinet = dv.getUint16(offset, true);
        offset += 2;

        let cbCFHeader = 0, cbCFFolder = 0, cbCFData = 0;
        if (flags & 0x0004) {
            cbCFHeader = dv.getUint16(offset, true);
            this.properties.cbCFHeader = cbCFHeader;
            offset += 2;
            cbCFFolder = dv.getUint8(offset);
            this.properties.cbCFFolder = cbCFFolder;
            offset += 1;
            cbCFData = dv.getUint8(offset);
            this.properties.cbCFData = cbCFData;
            offset += 1;
        }

        if (cbCFHeader > 0) {
            this.properties.abReserve_header = this.data.slice(offset, offset + cbCFHeader);
            offset += cbCFHeader;
        }

        if (flags & 0x0001) {
            const szCabinetPrev = this.readNullTermString(dv, offset);
            this.properties.szCabinetPrev = szCabinetPrev;
            offset += szCabinetPrev.length + 1;
            const szDiskPrev = this.readNullTermString(dv, offset);
            this.properties.szDiskPrev = szDiskPrev;
            offset += szDiskPrev.length + 1;
        }

        if (flags & 0x0002) {
            const szCabinetNext = this.readNullTermString(dv, offset);
            this.properties.szCabinetNext = szCabinetNext;
            offset += szCabinetNext.length + 1;
            const szDiskNext = this.readNullTermString(dv, offset);
            this.properties.szDiskNext = szDiskNext;
            offset += szDiskNext.length + 1;
        }

        // CFFOLDERs
        for (let i = 0; i < cFolders; i++) {
            const folder = {};
            folder.coffCabStart = dv.getUint32(offset, true);
            offset += 4;
            folder.cCFData = dv.getUint16(offset, true);
            offset += 2;
            folder.typeCompress = dv.getUint16(offset, true);
            offset += 2;
            if (cbCFFolder > 0) {
                folder.abReserve_folder = this.data.slice(offset, offset + cbCFFolder);
                offset += cbCFFolder;
            }
            this.folders.push(folder);
        }

        // CFFILEs
        offset = this.properties.coffFiles;
        for (let i = 0; i < cFiles; i++) {
            const fileEntry = {};
            fileEntry.cbFile = dv.getUint32(offset, true);
            offset += 4;
            fileEntry.uoffFolderStart = dv.getUint32(offset, true);
            offset += 4;
            fileEntry.iFolder = dv.getUint16(offset, true);
            offset += 2;
            fileEntry.date = dv.getUint16(offset, true);
            offset += 2;
            fileEntry.time = dv.getUint16(offset, true);
            offset += 2;
            fileEntry.attribs = dv.getUint16(offset, true);
            offset += 2;
            const szName = this.readNullTermString(dv, offset);
            fileEntry.szName = szName;
            offset += szName.length + 1;
            this.files.push(fileEntry);
        }

        // CFDATA blocks
        for (const folder of this.folders) {
            offset = folder.coffCabStart;
            for (let d = 0; d < folder.cCFData; d++) {
                const dataBlock = {};
                dataBlock.csum = dv.getUint32(offset, true);
                offset += 4;
                dataBlock.cbData = dv.getUint16(offset, true);
                const cbData = dataBlock.cbData;
                offset += 2;
                dataBlock.cbUncomp = dv.getUint16(offset, true);
                offset += 2;
                if (cbCFData > 0) {
                    dataBlock.abReserve_data = this.data.slice(offset, offset + cbCFData);
                    offset += cbCFData;
                }
                dataBlock.ab = this.data.slice(offset, offset + cbData);
                offset += cbData;
                this.dataBlocks.push(dataBlock);
            }
        }
    }

    readNullTermString(dv, offset) {
        let str = '';
        while (dv.getUint8(offset) !== 0) {
            str += String.fromCharCode(dv.getUint8(offset));
            offset++;
        }
        return str;
    }

    printProperties() {
        console.log('CFHEADER Properties:');
        for (const [key, value] of Object.entries(this.properties)) {
            if (value instanceof Buffer) {
                console.log(`  ${key}: [binary data, size ${value.length}]`);
            } else {
                console.log(`  ${key}: ${value}`);
            }
        }
        console.log('\nFolders:');
        this.folders.forEach((folder, i) => {
            console.log(`  Folder ${i}:`);
            for (const [key, value] of Object.entries(folder)) {
                if (value instanceof Buffer) {
                    console.log(`    ${key}: [binary data, size ${value.length}]`);
                } else {
                    console.log(`    ${key}: ${value}`);
                }
            }
        });
        console.log('\nFiles:');
        this.files.forEach((fileEntry, i) => {
            console.log(`  File ${i}:`);
            for (const [key, value] of Object.entries(fileEntry)) {
                console.log(`    ${key}: ${value}`);
            }
        });
        console.log('\nData Blocks:');
        this.dataBlocks.forEach((dataBlock, i) => {
            console.log(`  Data Block ${i}:`);
            for (const [key, value] of Object.entries(dataBlock)) {
                if (value instanceof Buffer) {
                    console.log(`    ${key}: [binary data, size ${value.length}]`);
                } else {
                    console.log(`    ${key}: ${value}`);
                }
            }
        });
    }

    write(outputPath) {
        fs.writeFileSync(outputPath, this.data);
        console.log(`Written to ${outputPath}`);
    }
}

// Example usage:
// const cab = new CabFile('sample.cab');
// cab.read();
// cab.printProperties();
// cab.write('output.cab');

7. C Class for .CAB File Handling

The following is a C++ class (as C does not have native classes; using struct with methods via class). It opens, decodes, reads, prints properties to console, and writes the file. Compile with a C++ compiler.

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstdint>
#include <cstring>

struct CabProperties {
    std::string signature;
    uint32_t reserved1;
    uint32_t cbCabinet;
    uint32_t reserved2;
    uint32_t coffFiles;
    uint8_t versionMinor;
    uint8_t versionMajor;
    uint16_t cFolders;
    uint16_t cFiles;
    uint16_t flags;
    uint16_t setID;
    uint16_t iCabinet;
    uint16_t cbCFHeader = 0;
    uint8_t cbCFFolder = 0;
    uint8_t cbCFData = 0;
    std::vector<uint8_t> abReserve_header;
    std::string szCabinetPrev;
    std::string szDiskPrev;
    std::string szCabinetNext;
    std::string szDiskNext;
};

struct CabFolder {
    uint32_t coffCabStart;
    uint16_t cCFData;
    uint16_t typeCompress;
    std::vector<uint8_t> abReserve_folder;
};

struct CabFileEntry {
    uint32_t cbFile;
    uint32_t uoffFolderStart;
    uint16_t iFolder;
    uint16_t date;
    uint16_t time;
    uint16_t attribs;
    std::string szName;
};

struct CabDataBlock {
    uint32_t csum;
    uint16_t cbData;
    uint16_t cbUncomp;
    std::vector<uint8_t> abReserve_data;
    std::vector<uint8_t> ab;
};

class CabFile {
private:
    std::string filepath;
    std::vector<uint8_t> data;
    CabProperties properties;
    std::vector<CabFolder> folders;
    std::vector<CabFileEntry> files;
    std::vector<CabDataBlock> data_blocks;

public:
    CabFile(const std::string& fp) : filepath(fp) {}

    void read() {
        std::ifstream file(filepath, std::ios::binary | std::ios::ate);
        if (!file) {
            std::cerr << "Failed to open file." << std::endl;
            return;
        }
        size_t size = file.tellg();
        file.seekg(0);
        data.resize(size);
        file.read(reinterpret_cast<char*>(data.data()), size);
        decode();
    }

    void decode() {
        size_t offset = 0;
        // CFHEADER
        properties.signature = std::string(reinterpret_cast<char*>(&data[offset]), 4);
        offset += 4;
        memcpy(&properties.reserved1, &data[offset], 4);
        offset += 4;
        memcpy(&properties.cbCabinet, &data[offset], 4);
        offset += 4;
        memcpy(&properties.reserved2, &data[offset], 4);
        offset += 4;
        memcpy(&properties.coffFiles, &data[offset], 4);
        offset += 4;
        properties.versionMinor = data[offset];
        offset += 1;
        properties.versionMajor = data[offset];
        offset += 1;
        memcpy(&properties.cFolders, &data[offset], 2);
        uint16_t cFolders = properties.cFolders;
        offset += 2;
        memcpy(&properties.cFiles, &data[offset], 2);
        uint16_t cFiles = properties.cFiles;
        offset += 2;
        memcpy(&properties.flags, &data[offset], 2);
        uint16_t flags = properties.flags;
        offset += 2;
        memcpy(&properties.setID, &data[offset], 2);
        offset += 2;
        memcpy(&properties.iCabinet, &data[offset], 2);
        offset += 2;

        if (flags & 0x0004) {
            memcpy(&properties.cbCFHeader, &data[offset], 2);
            offset += 2;
            properties.cbCFFolder = data[offset];
            offset += 1;
            properties.cbCFData = data[offset];
            offset += 1;
        }

        if (properties.cbCFHeader > 0) {
            properties.abReserve_header.assign(&data[offset], &data[offset + properties.cbCFHeader]);
            offset += properties.cbCFHeader;
        }

        if (flags & 0x0001) {
            properties.szCabinetPrev = read_null_term(offset);
            offset += properties.szCabinetPrev.length() + 1;
            properties.szDiskPrev = read_null_term(offset);
            offset += properties.szDiskPrev.length() + 1;
        }

        if (flags & 0x0002) {
            properties.szCabinetNext = read_null_term(offset);
            offset += properties.szCabinetNext.length() + 1;
            properties.szDiskNext = read_null_term(offset);
            offset += properties.szDiskNext.length() + 1;
        }

        // CFFOLDERs
        for (uint16_t i = 0; i < cFolders; ++i) {
            CabFolder folder;
            memcpy(&folder.coffCabStart, &data[offset], 4);
            offset += 4;
            memcpy(&folder.cCFData, &data[offset], 2);
            offset += 2;
            memcpy(&folder.typeCompress, &data[offset], 2);
            offset += 2;
            if (properties.cbCFFolder > 0) {
                folder.abReserve_folder.assign(&data[offset], &data[offset + properties.cbCFFolder]);
                offset += properties.cbCFFolder;
            }
            folders.push_back(folder);
        }

        // CFFILEs
        offset = properties.coffFiles;
        for (uint16_t i = 0; i < cFiles; ++i) {
            CabFileEntry file_entry;
            memcpy(&file_entry.cbFile, &data[offset], 4);
            offset += 4;
            memcpy(&file_entry.uoffFolderStart, &data[offset], 4);
            offset += 4;
            memcpy(&file_entry.iFolder, &data[offset], 2);
            offset += 2;
            memcpy(&file_entry.date, &data[offset], 2);
            offset += 2;
            memcpy(&file_entry.time, &data[offset], 2);
            offset += 2;
            memcpy(&file_entry.attribs, &data[offset], 2);
            offset += 2;
            file_entry.szName = read_null_term(offset);
            offset += file_entry.szName.length() + 1;
            files.push_back(file_entry);
        }

        // CFDATA blocks
        for (const auto& folder : folders) {
            offset = folder.coffCabStart;
            for (uint16_t d = 0; d < folder.cCFData; ++d) {
                CabDataBlock data_block;
                memcpy(&data_block.csum, &data[offset], 4);
                offset += 4;
                memcpy(&data_block.cbData, &data[offset], 2);
                uint16_t cbData = data_block.cbData;
                offset += 2;
                memcpy(&data_block.cbUncomp, &data[offset], 2);
                offset += 2;
                if (properties.cbCFData > 0) {
                    data_block.abReserve_data.assign(&data[offset], &data[offset + properties.cbCFData]);
                    offset += properties.cbCFData;
                }
                data_block.ab.assign(&data[offset], &data[offset + cbData]);
                offset += cbData;
                data_blocks.push_back(data_block);
            }
        }
    }

    std::string read_null_term(size_t offset) {
        std::string str;
        while (data[offset] != 0) {
            str += static_cast<char>(data[offset]);
            ++offset;
        }
        return str;
    }

    void print_properties() {
        std::cout << "CFHEADER Properties:" << std::endl;
        std::cout << "  signature: " << properties.signature << std::endl;
        std::cout << "  reserved1: " << properties.reserved1 << std::endl;
        std::cout << "  cbCabinet: " << properties.cbCabinet << std::endl;
        std::cout << "  reserved2: " << properties.reserved2 << std::endl;
        std::cout << "  coffFiles: " << properties.coffFiles << std::endl;
        std::cout << "  versionMinor: " << static_cast<int>(properties.versionMinor) << std::endl;
        std::cout << "  versionMajor: " << static_cast<int>(properties.versionMajor) << std::endl;
        std::cout << "  cFolders: " << properties.cFolders << std::endl;
        std::cout << "  cFiles: " << properties.cFiles << std::endl;
        std::cout << "  flags: " << properties.flags << std::endl;
        std::cout << "  setID: " << properties.setID << std::endl;
        std::cout << "  iCabinet: " << properties.iCabinet << std::endl;
        if (properties.flags & 0x0004) {
            std::cout << "  cbCFHeader: " << properties.cbCFHeader << std::endl;
            std::cout << "  cbCFFolder: " << static_cast<int>(properties.cbCFFolder) << std::endl;
            std::cout << "  cbCFData: " << static_cast<int>(properties.cbCFData) << std::endl;
        }
        if (!properties.abReserve_header.empty()) {
            std::cout << "  abReserve_header: [binary data, size " << properties.abReserve_header.size() << "]" << std::endl;
        }
        if (properties.flags & 0x0001) {
            std::cout << "  szCabinetPrev: " << properties.szCabinetPrev << std::endl;
            std::cout << "  szDiskPrev: " << properties.szDiskPrev << std::endl;
        }
        if (properties.flags & 0x0002) {
            std::cout << "  szCabinetNext: " << properties.szCabinetNext << std::endl;
            std::cout << "  szDiskNext: " << properties.szDiskNext << std::endl;
        }

        std::cout << "\nFolders:" << std::endl;
        for (size_t i = 0; i < folders.size(); ++i) {
            std::cout << "  Folder " << i << ":" << std::endl;
            std::cout << "    coffCabStart: " << folders[i].coffCabStart << std::endl;
            std::cout << "    cCFData: " << folders[i].cCFData << std::endl;
            std::cout << "    typeCompress: " << folders[i].typeCompress << std::endl;
            if (!folders[i].abReserve_folder.empty()) {
                std::cout << "    abReserve_folder: [binary data, size " << folders[i].abReserve_folder.size() << "]" << std::endl;
            }
        }

        std::cout << "\nFiles:" << std::endl;
        for (size_t i = 0; i < files.size(); ++i) {
            std::cout << "  File " << i << ":" << std::endl;
            std::cout << "    cbFile: " << files[i].cbFile << std::endl;
            std::cout << "    uoffFolderStart: " << files[i].uoffFolderStart << std::endl;
            std::cout << "    iFolder: " << files[i].iFolder << std::endl;
            std::cout << "    date: " << files[i].date << std::endl;
            std::cout << "    time: " << files[i].time << std::endl;
            std::cout << "    attribs: " << files[i].attribs << std::endl;
            std::cout << "    szName: " << files[i].szName << std::endl;
        }

        std::cout << "\nData Blocks:" << std::endl;
        for (size_t i = 0; i < data_blocks.size(); ++i) {
            std::cout << "  Data Block " << i << ":" << std::endl;
            std::cout << "    csum: " << data_blocks[i].csum << std::endl;
            std::cout << "    cbData: " << data_blocks[i].cbData << std::endl;
            std::cout << "    cbUncomp: " << data_blocks[i].cbUncomp << std::endl;
            if (!data_blocks[i].abReserve_data.empty()) {
                std::cout << "    abReserve_data: [binary data, size " << data_blocks[i].abReserve_data.size() << "]" << std::endl;
            }
            std::cout << "    ab: [binary data, size " << data_blocks[i].ab.size() << "]" << std::endl;
        }
    }

    void write(const std::string& output_path) {
        std::ofstream out(output_path, std::ios::binary);
        if (!out) {
            std::cerr << "Failed to write file." << std::endl;
            return;
        }
        out.write(reinterpret_cast<const char*>(data.data()), data.size());
        std::cout << "Written to " << output_path << std::endl;
    }
};

// Example usage:
// int main() {
//     CabFile cab("sample.cab");
//     cab.read();
//     cab.print_properties();
//     cab.write("output.cab");
//     return 0;
// }