moonmspack

    MoonBit CAB, CHM, MSZIP, and LZX archive reader

    cab
    chm
    lzx
    archive
    mspack
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    11 hours ago
    Downloads
    2

    Dependencies

    #MoonMSPack

    MoonBit 对 Microsoft Cabinet(CAB)和 Compiled HTML Help(CHM)格式的可嵌入解析库,移植自 kyz/libmspack 的相关设计与格式知识。

    当前实现阶段:防御式读取器、CAB 目录解析、CHM 头部识别和统一格式探测已完成;MSZIP/LZX 解码、CHM 索引遍历和提取 API 正在继续实现。

    #设计边界

    • 不替代 ZIP/TAR;不调用系统命令。
    • 所有偏移和长度在读取前检查。
    • API 接受内存 Bytes,方便 native、wasm-gc、wasm、js 共用。
    • 初始版本不宣称支持 libmspack 的全部格式。

    #示例

    let listing = @moonmspack.list(input)
    println(listing.format)
    for item in listing.entries { println(item.name) }

    #上游与许可证

    上游:https://github.com/kyz/libmspack ,相关源文件为 LGPL-2.1。该项目保留上游归属说明,并将 MoonBit 实现与上游代码分离记录。

    #三个可复用场景

    #1. Windows 安装包索引

    let listing = @moonmspack.read_cab(cab_bytes)
    for entry in listing.entries {
    println(entry.name)
    }

    适合在不调用外部解压程序的情况下,展示安装包中的文件清单。

    #2. 受限提取

    ///|
    let listing = @moonmspack.read_cab(cab_bytes)

    ///|
    let file = listing.entries[0]

    ///|
    let content = @moonmspack.extract_cab_entry(cab_bytes, file, limits={
    max_entries: 1000,
    max_output: 16 * 1024 * 1024,
    max_window: 32768,
    })

    所有文件表、块表、压缩输出和偏移都经过边界限制,适合服务端上传文件检查。

    #3. CHM 文档目录扫描

    let listing = @moonmspack.read_chm_entries(chm_bytes)
    for entry in listing.entries {
    println(entry.name)
    }

    适合文档离线索引、帮助文件迁移和格式审计。压缩 CHM 内容在 LZX 完整解码完成前会明确报告不支持,不会返回错误数据。

    #当前可量化验证

    • moon test 当前包含 3 个通过测试,覆盖格式探测、位读取、CAB Stored 列表与提取。
    • 所有公开读取入口均执行输入长度检查;CAB 提取受 Limits.max_output 限制。
    • 支持目标由 MoonBit 工具链统一构建,未依赖系统 cabextract 或 Windows API。

    MspackError

    pub suberror MspackError {
    MspackError(String)
    }

    Error categories returned by the archive readers.

    ArchiveEntry

    pub struct ArchiveEntry {
    name : String
    size : Int
    compressed_size : Int
    offset : Int
    folder : Int
    flags : Int
    } derive(Eq, Show)

    ArchiveListing

    pub struct ArchiveListing {
    format : String
    entries : Array[ArchiveEntry]
    warnings : Array[String]
    } derive(Show)

    ArchiveMetrics

    pub struct ArchiveMetrics {
    entry_count : Int
    total_size : Int
    total_compressed_size : Int
    largest_size : Int
    largest_index : Int
    folders : Int
    files_with_data : Int
    } derive(Eq, Show)

    Aggregate statistics for an archive listing.

    BitReader

    pub struct BitReader {
    data : Bytes
    byte_pos : Int
    bit_buffer : UInt
    bits : Int
    } derive(Show)

    BitReader::align_word

    fn BitReader::align_word(self : BitReader) -> Unit raise MspackError

    BitReader::bytes

    fn BitReader::bytes(self : BitReader, count : Int) -> (BitReader, Bytes) raise MspackError

    Read whole bytes after the current bit cursor is byte aligned.

    BitReader::new

    fn BitReader::new(data : Bytes) -> BitReader

    BitReader::read

    fn BitReader::read(self : BitReader, count : Int) -> Int raise MspackError

    CabBlock

    pub struct CabBlock {
    offset : Int
    checksum : UInt
    compressed_size : Int
    uncompressed_size : Int
    compression : Int
    } derive(Eq, Show)

    Metadata for one CAB CFDATA block.

    ChmDirectory

    pub struct ChmDirectory {
    version : Int
    chunk_size : Int
    first_chunk : Int
    last_chunk : Int
    listing_offset : Int
    listing_length : Int
    } derive(Eq, Show)

    ChmHeader

    pub struct ChmHeader {
    version : Int
    dir_offset : Int
    dir_length : Int
    data_offset : Int
    chunk_size : Int
    lang_id : Int
    num_chunks : Int
    } derive(Eq, Show)

    ChmSection

    pub struct ChmSection {
    index : Int
    offset : Int
    length : Int
    } derive(Eq, Show)

    A bounded CHM data section descriptor.

    Extracted

    pub struct Extracted {
    entry : ArchiveEntry
    data : Bytes
    } derive(Show)

    HuffmanTable

    pub struct HuffmanTable {
    lengths : Array[Int]
    codes : Array[Int]
    max_bits : Int
    } derive(Show)

    HuffmanTable::build

    fn HuffmanTable::build(lengths : Array[Int]) -> HuffmanTable raise MspackError

    HuffmanTable::decode

    fn HuffmanTable::decode(self : HuffmanTable, bits : BitReader) -> (BitReader, Int) raise MspackError

    Limits

    pub struct Limits {
    max_entries : Int
    max_output : Int
    max_window : Int
    } derive(Show)

    LzxBlockHeader

    pub struct LzxBlockHeader {
    kind : Int
    length : Int
    aligned : Bool
    } derive(Eq, Show)

    Parsed LZX block header.

    LzxConfig

    pub struct LzxConfig {
    window_bits : Int
    reset_interval : Int
    max_output : Int
    } derive(Eq, Show)

    Stateful LZX decoder configuration.

    LzxDecoder

    pub struct LzxDecoder {
    config : LzxConfig
    window : LzxWindow
    recent0 : Int
    recent1 : Int
    recent2 : Int
    produced : Int
    } derive(Show)

    Stateful LZX decoder.

    LzxDecoder::emit

    fn LzxDecoder::emit(self : LzxDecoder, token : LzxToken) -> Unit raise MspackError

    Emit one decoded token while updating decoder state.

    LzxDecoder::new

    fn LzxDecoder::new(config? : LzxConfig) -> LzxDecoder raise MspackError

    Create a stateful LZX decoder.

    LzxDecoder::output_size

    fn LzxDecoder::output_size(self : LzxDecoder) -> Int

    Return the number of bytes produced by a decoder.

    LzxDecoder::remember_distance

    fn LzxDecoder::remember_distance(self : LzxDecoder, distance : Int) -> Unit raise MspackError

    Record a newly used match distance in the repeated-offset queue.

    LzxDecoder::reset

    fn LzxDecoder::reset(self : LzxDecoder) -> Unit

    Reset the decoder window and repeated offsets.

    LzxToken

    pub enum LzxToken {
    Literal(Byte)
    Match(distance~ : Int, length~ : Int)
    } derive(Eq, Show)

    A decoded LZX token, either a literal byte or a window match.

    LzxWindow

    pub struct LzxWindow {
    size : Int
    position : Int
    data : Array[Byte]
    } derive(Show)

    LzxWindow::copy

    fn LzxWindow::copy(self : LzxWindow, distance : Int, length : Int) -> Unit raise MspackError

    LzxWindow::new

    fn LzxWindow::new(window_bits : Int) -> LzxWindow raise MspackError

    LzxWindow::put

    fn LzxWindow::put(self : LzxWindow, value : Byte) -> Unit

    Reader

    pub struct Reader {
    data : Bytes
    pos : Int
    } derive(Show)

    Reader::at

    fn Reader::at(self : Reader) -> Int

    Reader::bytes

    fn Reader::bytes(self : Reader, count : Int) -> (Reader, Bytes) raise MspackError

    Reader::cstring

    fn Reader::cstring(self : Reader, count : Int) -> (Reader, String) raise MspackError

    Reader::new

    fn Reader::new(data : Bytes) -> Reader

    Reader::remaining

    fn Reader::remaining(self : Reader) -> Int

    Reader::seek

    fn Reader::seek(self : Reader, offset : Int) -> Reader raise MspackError

    Reader::skip

    fn Reader::skip(self : Reader, count : Int) -> Reader raise MspackError

    Reader::u16le

    fn Reader::u16le(self : Reader) -> (Reader, Int) raise MspackError

    Reader::u32le

    fn Reader::u32le(self : Reader) -> (Reader, UInt) raise MspackError

    Reader::u8

    fn Reader::u8(self : Reader) -> (Reader, Int) raise MspackError

    archive_format_label

    fn archive_format_label(listing : ArchiveListing) -> String

    Return a compact human-readable format label for diagnostics.

    archive_metrics

    fn archive_metrics(listing : ArchiveListing) -> ArchiveMetrics

    Calculate stable, allocation-free metrics for a parsed listing.

    archive_name_digest

    fn archive_name_digest(listing : ArchiveListing) -> UInt

    Compute a deterministic 32-bit FNV-1a digest for entry names.

    build_lzx_tree

    fn build_lzx_tree(lengths : Array[Int]) -> HuffmanTable raise MspackError

    Build and validate a decoding tree from serialized code lengths.

    checksum32

    fn checksum32(data : Bytes) -> UInt

    chm_section

    fn chm_section(header : ChmHeader, index : Int, length : Int, limits? : Limits) -> ChmSection raise MspackError

    Describe a CHM section using the absolute data offset.

    container_size

    fn container_size(listing : ArchiveListing, container : Int) -> Int

    Return the sum of sizes for one container.

    copy_range

    fn copy_range(data : Bytes, offset~ : Int, size~ : Int, limits? : Limits) -> Bytes raise MspackError

    count_entries_with_flags

    fn count_entries_with_flags(listing : ArchiveListing, mask : Int) -> Int

    Return the number of entries with a given compression flag mask.

    decode_lzx_block

    fn decode_lzx_block(data : Bytes, window_bits~ : Int, expected~ : Int, limits? : Limits) -> Bytes raise MspackError

    decode_lzx_code_lengths

    fn decode_lzx_code_lengths(bits : BitReader, pretree : HuffmanTable, count : Int, previous? : Int) -> (BitReader, Array[Int]) raise MspackError

    Decode a run of LZX code lengths using a pretree.

    decode_lzx_tokens

    fn decode_lzx_tokens(tokens : Array[LzxToken], config? : LzxConfig) -> Bytes raise MspackError

    Decode a token sequence with persistent repeated-offset state.

    decode_lzx_tree

    fn decode_lzx_tree(bits : BitReader, pretree : HuffmanTable, symbols : Int) -> (BitReader, HuffmanTable) raise MspackError

    Decode a complete tree from a pretree-coded length stream.

    decode_mszip_block

    fn decode_mszip_block(data : Bytes, expected~ : Int, limits? : Limits) -> Bytes raise MspackError

    default_limits

    let default_limits : Limits

    default_lzx_config

    let default_lzx_config : LzxConfig

    Default safe LZX configuration.

    detect

    fn detect(data : Bytes) -> String

    emit_lzx_tokens

    fn emit_lzx_tokens(window : LzxWindow, tokens : Array[LzxToken], limits? : Limits) -> Bytes raise MspackError

    Emit decoded tokens into a sliding LZX window.

    entries_by_size

    fn entries_by_size(listing : ArchiveListing, minimum : Int, maximum : Int) -> Array[ArchiveEntry]

    Select entries whose uncompressed size is within a caller supplied range.

    entries_in_container

    fn entries_in_container(listing : ArchiveListing, container : Int) -> Array[ArchiveEntry]

    Return all entries belonging to one CAB folder or CHM section.

    entry_view

    fn entry_view(data : Bytes, entry : ArchiveEntry, limits? : Limits) -> Reader raise MspackError

    Create a bounded reader view for a single archive entry.

    extract_cab_entry

    fn extract_cab_entry(data : Bytes, entry : ArchiveEntry, limits? : Limits) -> Bytes raise MspackError

    extract_chm_entry

    fn extract_chm_entry(data : Bytes, header : ChmHeader, entry : ArchiveEntry, limits? : Limits) -> Bytes raise MspackError

    Extract an entry from an uncompressed CHM section.

    find_archive_entry

    fn find_archive_entry(listing : ArchiveListing, name : String) -> ArchiveEntry?

    Find an entry by its exact archive path.

    has_unique_entry_names

    fn has_unique_entry_names(listing : ArchiveListing) -> Bool

    Return true when all entry names are unique.

    inspect_cab_blocks

    fn inspect_cab_blocks(data : Bytes, entry : ArchiveEntry, limits? : Limits) -> Array[CabBlock] raise MspackError

    Inspect CFDATA blocks without decompressing them.

    inspect_chm

    fn inspect_chm(data : Bytes) -> (ChmHeader, ChmDirectory) raise MspackError

    inspect_lzx_block

    fn inspect_lzx_block(data : Bytes, limits? : Limits) -> LzxBlockHeader raise MspackError

    Read and validate a bounded LZX block header.

    list

    fn list(data : Bytes, limits? : Limits) -> ArchiveListing raise MspackError

    list_cab

    fn list_cab(data : Bytes) -> Array[ArchiveEntry] raise MspackError

    list_chm_directory

    fn list_chm_directory(data : Bytes, header : ChmHeader, directory : ChmDirectory, limits? : Limits) -> Array[ArchiveEntry] raise MspackError

    Enumerate uncompressed CHM PMGL entries in the directory stream.

    lzx_aligned_symbols

    let lzx_aligned_symbols : Int

    lzx_block_aligned

    let lzx_block_aligned : Int

    lzx_block_uncompressed

    let lzx_block_uncompressed : Int

    lzx_block_verbatim

    let lzx_block_verbatim : Int

    lzx_frame_size

    let lzx_frame_size : Int

    lzx_header_bytes

    fn lzx_header_bytes() -> Int

    Return the LZX block header size in bytes for diagnostics.

    lzx_literal

    fn lzx_literal(byte : Byte) -> LzxToken

    Construct a literal token for callers outside the package.

    lzx_match

    fn lzx_match(distance~ : Int, length~ : Int) -> LzxToken

    Construct a match token for callers outside the package.

    lzx_match_distance

    fn lzx_match_distance(slot : Int, footer : Int, recent0 : Int, recent1 : Int, recent2 : Int, window_bits : Int) -> Int raise MspackError

    Convert an LZX slot and footer into a match distance.

    lzx_match_length

    fn lzx_match_length(symbol : Int) -> Int raise MspackError

    Convert an LZX length symbol into a match length.

    lzx_max_match

    let lzx_max_match : Int

    lzx_min_match

    let lzx_min_match : Int

    LZX format constants shared by block decoders.

    lzx_num_chars

    let lzx_num_chars : Int

    lzx_position_extra_bits

    fn lzx_position_extra_bits(window_bits : Int) -> Array[Int] raise MspackError

    Build the extra-bit table for the LZX position slots.

    lzx_position_slots

    fn lzx_position_slots(window_bits : Int) -> Int raise MspackError

    Return the number of position slots for an LZX window.

    lzx_pretree_symbols

    let lzx_pretree_symbols : Int

    lzx_primary_lengths

    let lzx_primary_lengths : Int

    lzx_secondary_lengths

    let lzx_secondary_lengths : Int

    lzx_token_from_symbols

    fn lzx_token_from_symbols(main_symbol : Int, length_symbol : Int, distance : Int) -> LzxToken raise MspackError

    Decode an LZX symbol pair into a literal or match token.

    lzx_uses_aligned_tree

    fn lzx_uses_aligned_tree(block_type : Int) -> Bool

    Return whether a block type uses the aligned offset tree.

    read_cab

    fn read_cab(data : Bytes, limits? : Limits) -> ArchiveListing raise MspackError

    read_chm

    fn read_chm(data : Bytes, limits? : Limits) -> ArchiveListing raise MspackError

    read_chm_entries

    fn read_chm_entries(data : Bytes, limits? : Limits) -> ArchiveListing raise MspackError

    Read a CHM header and enumerate its uncompressed directory entries.

    read_chm_section

    fn read_chm_section(data : Bytes, section : ChmSection, relative : Int, length : Int, limits? : Limits) -> Bytes raise MspackError

    Read a bounded range from a CHM section.

    read_lzx_block_header

    fn read_lzx_block_header(bits : BitReader) -> (BitReader, LzxBlockHeader) raise MspackError

    Read an LZX block header from a bit reader.

    summary

    fn summary(listing : ArchiveListing) -> String

    Return a stable human-readable archive summary.

    take_archive_entries

    fn take_archive_entries(listing : ArchiveListing, count : Int) -> Array[ArchiveEntry]

    Return entries in their original archive order, limited by count.

    validate_cab_entry_blocks

    fn validate_cab_entry_blocks(data : Bytes, entry : ArchiveEntry, limits? : Limits) -> Array[CabBlock] raise MspackError

    Inspect and validate every block belonging to a CAB entry.

    validate_entry_identity

    fn validate_entry_identity(listing : ArchiveListing) -> Unit raise MspackError

    Verify that all entries have a non-empty name and valid container id.

    validate_entry_ranges

    fn validate_entry_ranges(listing : ArchiveListing, input_size : Int) -> Unit raise MspackError

    Check that offsets and sizes do not overflow an input buffer.

    validate_listing

    fn validate_listing(listing : ArchiveListing, limits? : Limits) -> Unit raise MspackError

    Validate listing metadata before an extraction loop.

    validate_lzx_block_header

    fn validate_lzx_block_header(header : LzxBlockHeader, limits? : Limits) -> Unit raise MspackError

    Validate an LZX block header against configured output limits.

    validate_lzx_tokens

    fn validate_lzx_tokens(tokens : Array[LzxToken]) -> Unit raise MspackError

    Validate a token stream before emission.

    validate_lzx_tree

    fn validate_lzx_tree(lengths : Array[Int]) -> Unit raise MspackError

    Validate a Huffman tree before it is used for an LZX block.

    verify_cab_block_checksum

    fn verify_cab_block_checksum(data : Bytes, block : CabBlock) -> Bool raise MspackError

    Validate the checksum field of a CAB CFDATA block.

    within_output_budget

    fn within_output_budget(listing : ArchiveListing, budget : Int) -> Bool

    Check whether extraction can proceed without exceeding a byte budget.