///|
pub struct ChmHeader {
pub version : Int
pub dir_offset : Int
pub dir_length : Int
pub data_offset : Int
pub chunk_size : Int
pub lang_id : Int
pub num_chunks : Int
} derive(Show, Eq)
///|
fn chm_u64(reader : Reader) -> (Reader, Int) raise MspackError {
let (reader, low) = reader.u32le()
let (reader, high) = reader.u32le()
if high != 0 {
raise MspackError("CHM 64-bit offset exceeds supported range")
}
(reader, low.to_int())
}
///|
fn parse_chm_header(data : Bytes) -> ChmHeader raise MspackError {
if data.length() < 0x58 {
raise MspackError("truncated CHM header")
}
let r = Reader::new(data)
let (r, a) = r.u8()
let (r, b) = r.u8()
let (r, c) = r.u8()
let (r, d) = r.u8()
if a != 0x49 || b != 0x54 || c != 0x53 || d != 0x46 {
raise MspackError("invalid CHM magic")
}
let (r, version) = r.u32le()
let (r, header_length) = r.u32le()
let (r, _) = r.u32le()
let (r, _) = r.u32le()
let (r, lang_id) = r.u32le()
let r = r.skip(32)
let (r, _) = chm_u64(r)
let (r, _) = chm_u64(r)
let (r, dir_offset) = chm_u64(r)
let (r, dir_length) = chm_u64(r)
let (r, data_offset) = chm_u64(r)
if header_length.to_int() < 0x58 || header_length.to_int() > data.length() {
raise MspackError("invalid CHM header length")
}
checked_range(data.length(), dir_offset, dir_length)
{
version: version.to_int(),
dir_offset,
dir_length,
data_offset,
chunk_size: 0,
lang_id: lang_id.to_int(),
num_chunks: 0,
}
}
///|
pub fn read_chm(
data : Bytes,
limits? : Limits = default_limits,
) -> ArchiveListing raise MspackError {
let header = parse_chm_header(data)
if header.dir_length > limits.max_output {
raise MspackError("CHM directory exceeds limit")
}
let warnings = if header.version != 3 { ["unusual CHM version"] } else { [] }
{ format: "CHM", entries: [], warnings, }
}
///|
pub fn detect(data : Bytes) -> String {
if data.length() >= 4 &&
data[0] == 0x4D &&
data[1] == 0x53 &&
data[2] == 0x43 &&
data[3] == 0x46 {
"CAB"
} else if data.length() >= 4 &&
data[0] == 0x49 &&
data[1] == 0x54 &&
data[2] == 0x53 &&
data[3] == 0x46 {
"CHM"
} else {
"unknown"
}
}
///|
pub fn list(
data : Bytes,
limits? : Limits = default_limits,
) -> ArchiveListing raise MspackError {
match detect(data) {
"CAB" => read_cab(data, limits~)
"CHM" => read_chm(data, limits~)
_ => raise MspackError("unknown archive format")
}
}
///|
pub struct ChmDirectory {
pub version : Int
pub chunk_size : Int
pub first_chunk : Int
pub last_chunk : Int
pub listing_offset : Int
pub listing_length : Int
} derive(Show, Eq)
///|
fn read_u32_at(data : Bytes, offset : Int) -> UInt raise MspackError {
let r = Reader::new(data).seek(offset)
let (_, value) = r.u32le()
value
}
///|
fn read_chm_directory_header(
data : Bytes,
header : ChmHeader,
) -> ChmDirectory raise MspackError {
checked_range(data.length(), header.dir_offset, 0x38)
let r = Reader::new(data).seek(header.dir_offset)
let (r, a) = r.u8()
let (r, b) = r.u8()
let (r, c) = r.u8()
let (r, d) = r.u8()
if a != 0x49 || b != 0x54 || c != 0x53 || d != 0x50 {
raise MspackError("invalid CHM ITSP signature")
}
let (r, version) = r.u32le()
let (r, header_len) = r.u32le()
let (r, chunk_size) = r.u32le()
let (r, density) = r.u32le()
let (r, depth) = r.u32le()
let (r, root) = r.u32le()
let (r, first) = r.u32le()
let (r, last) = r.u32le()
let (r, next) = r.u32le()
let (r, num_chunks) = r.u32le()
if header_len.to_int() < 0x38 || header_len.to_int() > header.dir_length {
raise MspackError("invalid CHM ITSP header length")
}
if chunk_size.to_int() < 0x100 || chunk_size.to_int() > 0x100000 {
raise MspackError("unsupported CHM chunk size")
}
if density.to_int() > 0x10000 || depth.to_int() > 0x10000 {
raise MspackError("invalid CHM index parameters")
}
if first.to_int() > last.to_int() && last.to_int() != 0xFFFFFFFF {
raise MspackError("invalid CHM chunk range")
}
if num_chunks.to_int() > 100000000 {
raise MspackError("CHM chunk count exceeds limit")
}
{
version: version.to_int(),
chunk_size: chunk_size.to_int(),
first_chunk: first.to_int(),
last_chunk: last.to_int(),
listing_offset: header.dir_offset + header_len.to_int(),
listing_length: header.dir_length - header_len.to_int(),
}
}
///|
pub fn inspect_chm(data : Bytes) -> (ChmHeader, ChmDirectory) raise MspackError {
let header = parse_chm_header(data)
(header, read_chm_directory_header(data, header))
}