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.
2. Two Direct Download Links for .CAB Files
(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.
Drag and Drop .CAB File
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;
// }