All formats

libchm

LanguageRust (1.87+)
LicenseMIT
Repositorygithub.com/trypsynth/libchm
Documentationdocs.rs/libchm
Depends onnothing (own LZX decoder, no unsafe code)
Read / writeyes / no

libchm opens a CHM file, lists its directory, and reads entries, decompressing the LZX section as needed. The API is small: ChmFile::open, entries, entries_in, find, read and read_path. Each Entry has its path, uncompressed length, kind (file or directory) and category (normal, special or metadata).

CHM files often come from untrusted sources; libchm checks every header value that drives an allocation, index or division, and reports bad files as ChmError. The crate is new (2025) and at version 0.x. A Python wrapper, pychmrs, is built on it.

Installation#

Shell
cargo add libchm

Basic usage: list files and read a page#

Rust
use libchm::{ChmFile, EntrySel};

fn main() -> libchm::Result<()> {
    let mut chm = ChmFile::open("help.chm")?;

    // ordinary content files, without directories and internal metadata
    for entry in chm.entries(EntrySel::NORMAL | EntrySel::FILES)? {
        println!("{:>8}  {}", entry.length, entry.path);
    }

    let html = chm.read_path("/index.html")?; // case-insensitive, absolute path
    println!("{}", String::from_utf8_lossy(&html));
    Ok(())
}

How to check a file before reading it#

find returns the entry’s metadata without decompressing it:

Rust
let entry = chm.find("/index.html")?;
println!("{} bytes, compressed: {}", entry.length, entry.is_compressed());
let bytes = chm.read(&entry)?;

How to extract all files from a CHM#

Rust
use libchm::{ChmFile, EntrySel};
use std::{fs, path::Path};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut chm = ChmFile::open("help.chm")?;
    let out = Path::new("out");
    for entry in chm.entries(EntrySel::NORMAL | EntrySel::FILES)? {
        let rel = entry.path.trim_start_matches('/');
        if rel.split('/').any(|part| part == "..") {
            continue; // skip paths that escape the output folder
        }
        let dest = out.join(rel);
        if let Some(parent) = dest.parent() {
            fs::create_dir_all(parent)?;
        }
        fs::write(&dest, chm.read(&entry)?)?;
    }
    Ok(())
}

How to list one folder or the internal files#

Rust
// everything under /images
let images = chm.entries_in("/images", EntrySel::NORMAL | EntrySel::FILES)?;

// internal files such as /#SYSTEM, /#TOPICS, /$FIftiMain
let special = chm.entries(EntrySel::SPECIAL | EntrySel::FILES)?;

EntrySel must name both a category (NORMAL, SPECIAL, META) and a kind (FILES, DIRS); EntrySel::ALL selects everything.

Tips#