Sign in

    moonmspack

    MoonBit CAB, CHM, MSZIP, and LZX archive reader

    cab
    chm
    lzx
    archive
    mspack
    Download zip
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    24 days ago
    Downloads
    16

    #MoonMSPack

    MoonBit CI

    MoonMSPack 是面向 MoonBit 生态的 Microsoft Cabinet(CAB)与 Compiled HTML Help(CHM)解析库。它适合安装包索引、帮助文档迁移和不可信归档的受限检查;所有偏移、长度、条目数和输出大小都在读取前进行验证,不调用系统解压命令。

    项目参考 libmspack 的格式知识和接口设计,以 MoonBit 独立实现。许可证边界见 THIRD_PARTY_NOTICES.md。

    #当前能力

    能力状态
    CAB 文件头、Folder、File、CFDATA 解析已实现
    CAB Stored 条目提取已实现
    CAB MSZIP 块解压与校验已实现
    CHM ITSF、ITSP、PMGL 目录解析已实现
    CHM 未压缩 Section 0 条目提取已实现
    LZX 位读取、Huffman 表、窗口、match/token 和 block header已实现
    完整 LZX verbatim/aligned 压缩流解码尚未完成,会明确返回错误
    跨 cabinet 集合和写入 CAB/CHM不在 0.1.x 范围内

    项目不会在尚未支持的压缩路径上返回伪造数据。

    #环境要求

    • moonc >= 0.10.14
    • moon 包管理与构建工具
    • 支持 MoonBit wasm 目标的环境

    检查版本:

    moon version --all

    本项目开发验收使用:

    moonc v0.10.14+7d59c7ec9

    #安装

    从 MoonCakes 安装已发布版本:

    moon add TWISTY2Ynb/moonmspack@0.1.1

    或从源码验证:

    git clone https://github.com/TWISTY2Ynb/moonmspack.git cd moonmspack moon update moon check moon build moon test

    #最小可运行示例

    仓库内置了一个不依赖外部文件的最小 CAB 示例。它构造一个 Stored CAB、列出 a.txt,再提取内容:

    moon run cmd/main

    预期输出:

    CAB: 1 entries b"a.txt" (4 bytes) payload: b"ABCD"

    核心代码如下:

    let listing = @moonmspack.read_cab(cab_bytes)
    println(@moonmspack.summary(listing))
    let entry = listing.entries[0]
    let content = @moonmspack.extract_cab_entry(cab_bytes, entry)

    #使用示例

    #示例一:索引 Windows CAB 安装包

    let listing = @moonmspack.read_cab(cab_bytes)
    @moonmspack.validate_listing(listing)
    for entry in listing.entries {
    println(entry.name + ": " + entry.size.to_string())
    }
    let metrics = @moonmspack.archive_metrics(listing)
    println("entries=" + metrics.entry_count.to_string())

    适用于软件资产清点、安装包内容预览和制品索引。

    #示例二:限制资源后提取 CAB 条目

    let limits : @moonmspack.Limits = {
    max_entries: 1000,
    max_output: 16 * 1024 * 1024,
    max_window: 32768,
    }
    let listing = @moonmspack.read_cab(cab_bytes, limits~)
    match @moonmspack.find_archive_entry(listing, "b\"a.txt\"") {
    Some(entry) => {
    let content = @moonmspack.extract_cab_entry(cab_bytes, entry, limits~)
    println(content.length())
    }
    None => println("entry not found")
    }

    Limits 同时约束目录规模、单次输出和 LZX 窗口,适合处理上传文件等不可信输入。

    #示例三:扫描并读取未压缩 CHM 内容

    let (header, _) = @moonmspack.inspect_chm(chm_bytes)
    let listing = @moonmspack.read_chm_entries(chm_bytes)
    for entry in listing.entries {
    println(entry.name)
    if entry.folder == 0 {
    let content = @moonmspack.extract_chm_entry(chm_bytes, header, entry)
    println(content.length())
    }
    }

    PMGL 目录中的 Section 0 可以直接读取;压缩 section 会明确提示需要完整 LZX 解码。

    #公共 API 概览

    • 格式探测:detect、list
    • CAB:read_cab、list_cab、inspect_cab_blocks、extract_cab_entry
    • CHM:inspect_chm、read_chm_entries、list_chm_directory、extract_chm_entry
    • MSZIP:decode_mszip_block
    • LZX 基础设施:BitReader、HuffmanTable、LzxWindow、LzxDecoder
    • 安全与统计:Limits、validate_listing、archive_metrics、find_archive_entry

    完整公开接口由 moon info 生成在 pkg.generated.mbti 中。

    #测试与持续集成

    本地验收命令:

    moon fmt --check moon check moon build moon test moon run cmd/main

    GitHub Actions 对每次 push 和 pull request 执行:

    1. 安装并校验 moonc >= 0.10.14;
    2. 更新依赖;
    3. 格式检查;
    4. 静态检查;
    5. 构建;
    6. 单元测试;
    7. 运行最小示例。

    测试覆盖格式探测、CAB Stored 列表与提取、CFDATA 元数据、CHM 目录与 Section 0 提取、边界拒绝、Huffman 解码、LZX token/window 和 decoder 状态重置。

    #实现质量与对标

    指标MoonMSPack 0.1.x对标说明
    系统命令依赖0不依赖 cabextract、Windows API 或外部进程
    输入入口边界检查公开读取/提取入口均检查面向服务端不可信输入
    可配置资源限制条目数、输出字节、LZX 窗口避免无界分配
    CAB / CHM 双格式同一 MoonBit 包对标 libmspack 的 CAB/CHM 方向
    自动化验收check、build、test、example每次提交由 GitHub Actions 复现
    运行目标MoonBit wasm 优先无 C FFI,便于跨平台嵌入

    libmspack 是功能完整度对标对象;MoonMSPack 0.1.x 聚焦安全解析、目录索引、Stored/MSZIP CAB 提取和未压缩 CHM 内容读取,不宣称与其功能完全等价。

    #许可证

    MoonMSPack 使用 OSI 认可的 Apache License 2.0。

    • 本仓库代码为独立 MoonBit 实现;
    • 未复制 libmspack 的 LGPL C 源文件;
    • 上游归属、许可证关系和依赖说明见 THIRD_PARTY_NOTICES.md。

    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.