///|
pub fn read_cab(
data : Bytes,
limits? : Limits = default_limits,
) -> ArchiveListing raise MspackError {
let r = Reader::new(data)
let (r, m0) = r.u8()
let (r, m1) = r.u8()
let (r, m2) = r.u8()
let (r, _) = r.u8()
if m0 != 0x4D || m1 != 0x53 || m2 != 0x43 {
raise MspackError("invalid CAB magic")
}
let (r, _) = r.u32le()
let (r, cb_cabinet) = r.u32le()
let (r, _) = r.u32le()
let (r, coff_files) = r.u32le()
let (r, _) = r.u32le()
let (r, _) = r.u8()
let (r, _) = r.u8()
let (r, c_folders) = r.u16le()
let (r, c_files) = r.u16le()
let (r, flags) = r.u16le()
let (r, _) = r.u16le()
let (r, _) = r.u16le()
if c_files > limits.max_entries {
raise MspackError("archive limit exceeded")
}
if cb_cabinet.to_int() > data.length() {
raise MspackError("archive bounds error")
}
let mut rr = r
if (flags & 0x0001) != 0 {
let (x, cb_prev) = rr.u16le()
let (x, _) = x.u16le()
let (x, _) = x.u16le()
let (x, _) = x.cstring(cb_prev)
rr = x
}
if (flags & 0x0002) != 0 {
let (x, cb_next) = rr.u16le()
let (x, _) = x.u16le()
let (x, _) = x.u16le()
let (x, _) = x.cstring(cb_next)
rr = x
}
if (flags & 0x0004) != 0 {
let (x, _) = rr.u16le()
let (x, _) = x.u16le()
rr = x
}
let folder_start = rr.at()
let folders = []
for _ in 0.. Array[ArchiveEntry] raise MspackError {
read_cab(data).entries
}
///|
fn cab_folders(data : Bytes) -> Array[(Int, Int, Int)] raise MspackError {
let r = Reader::new(data)
let (r, m0) = r.u8()
let (r, m1) = r.u8()
let (r, m2) = r.u8()
let (r, _) = r.u8()
if m0 != 0x4D || m1 != 0x53 || m2 != 0x43 {
raise MspackError("invalid CAB magic")
}
let (r, _) = r.u32le()
let (r, _) = r.u32le()
let (r, _) = r.u32le()
let (r, _) = r.u32le()
let (r, _) = r.u32le()
let (r, _) = r.u8()
let (r, _) = r.u8()
let (r, count) = r.u16le()
let (r, _) = r.u16le()
let (r, flags) = r.u16le()
let (r, _) = r.u16le()
let (r, _) = r.u16le()
let mut rr = r
if (flags & 1) != 0 {
let (x, n) = rr.u16le()
let (x, _) = x.u16le()
let (x, _) = x.u16le()
let x = x.skip(n)
rr = x
}
if (flags & 2) != 0 {
let (x, n) = rr.u16le()
let (x, _) = x.u16le()
let (x, _) = x.u16le()
let x = x.skip(n)
rr = x
}
if (flags & 4) != 0 {
let (x, _) = rr.u16le()
let (x, _) = x.u16le()
rr = x
}
let result : Array[(Int, Int, Int)] = []
for _ in 0.. Bytes raise MspackError {
let folders = cab_folders(data)
if entry.folder < 0 || entry.folder >= folders.length() {
raise MspackError("CAB folder index outside table")
}
let (offset, block_count, compression) = folders[entry.folder]
println(
"DBG " +
offset.to_string() +
" " +
block_count.to_string() +
" " +
entry.offset.to_string(),
)
let mut cursor = offset
let output : Array[Byte] = []
for _ in 0.. limits.max_output {
raise MspackError("CAB output exceeds limit")
}
for i in 0.. output.length() {
raise MspackError("CAB entry range exceeds decoded folder")
}
let selected : Array[Byte] = []
for i in begin..<(begin + entry.size) {
selected.push(output[i])
}
Bytes::from_array(selected)
}
///|
/// Metadata for one CAB CFDATA block.
pub struct CabBlock {
pub offset : Int
pub checksum : UInt
pub compressed_size : Int
pub uncompressed_size : Int
pub compression : Int
} derive(Show, Eq)
///|
/// Inspect CFDATA blocks without decompressing them.
pub fn inspect_cab_blocks(
data : Bytes,
entry : ArchiveEntry,
limits? : Limits = default_limits,
) -> Array[CabBlock] raise MspackError {
let folders = cab_folders(data)
if entry.folder < 0 || entry.folder >= folders.length() {
raise MspackError("CAB folder index outside table")
}
let (offset, block_count, compression) = folders[entry.folder]
if block_count > limits.max_entries {
raise MspackError("CAB block count exceeds limit")
}
let result : Array[CabBlock] = []
let mut cursor = offset
for _ in 0.. limits.max_output || compressed > limits.max_output {
raise MspackError("CAB block exceeds limit")
}
let (r, _) = r.bytes(compressed)
cursor = r.at()
result.push({
offset: block_offset,
checksum,
compressed_size: compressed,
uncompressed_size: uncompressed,
compression: compression & 0x000F,
})
}
result
}
///|
/// Validate the checksum field of a CAB CFDATA block.
pub fn verify_cab_block_checksum(
data : Bytes,
block : CabBlock,
) -> Bool raise MspackError {
let header_size = 8
checked_range(
data.length(),
block.offset,
header_size + block.compressed_size,
)
let payload = copy_range(
data,
offset=block.offset + header_size,
size=block.compressed_size,
)
checksum32(payload) == block.checksum || block.checksum == 0
}
///|
/// Inspect and validate every block belonging to a CAB entry.
pub fn validate_cab_entry_blocks(
data : Bytes,
entry : ArchiveEntry,
limits? : Limits = default_limits,
) -> Array[CabBlock] raise MspackError {
let blocks = inspect_cab_blocks(data, entry, limits~)
for block in blocks {
if !verify_cab_block_checksum(data, block) {
raise MspackError("CAB CFDATA checksum mismatch")
}
}
blocks
}