libmspack
| Language | C |
|---|---|
| License | LGPL-2.1 |
| Repository | github.com/kyz/libmspack |
| Documentation | API reference, CHM decompressor |
| Depends on | nothing |
| Read / write | yes / no (a CHM compressor interface exists but isn't implemented) |
libmspack implements the compression methods and containers Microsoft uses: CAB, CHM, LIT, HLP, KWAJ, SZDD and OAB, with LZX, Quantum, MSZIP and LZSS decoders. It’s the library inside cabextract and ClamAV and is maintained by Stuart Caie, who also wrote the LZX decoder used by CHMLib.
For CHM it lists all files with their sizes and sections, extracts files to disk, and has a fast mode that opens only the headers and finds single files through the CHM’s own index chunks. All file I/O goes through a replaceable mspack_system structure, so you can read from memory or write to your own streams. Errors come back as codes instead of crashes on malformed input.
Installation#
# Debian / Ubuntu
sudo apt install libmspack-dev
# Fedora
sudo dnf install libmspack-devel
# Windows / cross-platform
vcpkg install libmspack
Compile with cc app.c -lmspack (or pkg-config --libs libmspack).
Basic usage: list and extract all files#
#include <mspack.h>
#include <stdio.h>
int main(void) {
int selftest;
MSPACK_SYS_SELFTEST(selftest); /* checks the library was built with matching types */
if (selftest != MSPACK_ERR_OK)
return 1;
struct mschm_decompressor *chmd = mspack_create_chm_decompressor(NULL);
if (!chmd)
return 1;
struct mschmd_header *chm = chmd->open(chmd, "help.chm");
if (!chm) {
fprintf(stderr, "open failed: error %d\n", chmd->last_error(chmd));
mspack_destroy_chm_decompressor(chmd);
return 1;
}
for (struct mschmd_file *f = chm->files; f; f = f->next) {
printf("%10lld %s\n", (long long)f->length, f->filename);
/* f->filename starts with "/"; map it to a local path you trust */
char out[1024];
snprintf(out, sizeof out, "out%s", f->filename);
/* create parent directories of out here */
if (chmd->extract(chmd, f, out) != MSPACK_ERR_OK)
fprintf(stderr, "extract failed: %s\n", f->filename);
}
chmd->close(chmd, chm);
mspack_destroy_chm_decompressor(chmd);
return 0;
}
chm->files lists user files; chm->sysfiles lists the internal files (::DataSpace/..., /#SYSTEM and others).
How to extract files faster#
Files in the compressed section are faster to extract in the order they are stored, because LZX data is decompressed sequentially. Sort them by offset before extracting, as the chmextract example in the repository does:
static int by_offset(const void *a, const void *b) {
off_t diff = (*(struct mschmd_file **)a)->offset - (*(struct mschmd_file **)b)->offset;
return diff < 0 ? -1 : diff > 0;
}
/* collect chm->files into an array, qsort(array, n, sizeof *array, by_offset), extract in order */
How to read the CHM header information#
printf("version %u, language 0x%04x, %lld bytes\n",
chm->version, chm->language, (long long)chm->length);
How to look up one file without reading the whole directory#
fast_open reads only the headers; fast_find uses the index chunks to locate a file:
struct mschmd_header *chm = chmd->fast_open(chmd, "help.chm");
struct mschmd_file file;
if (chm && chmd->fast_find(chmd, chm, "/index.html", &file, sizeof(file)) == MSPACK_ERR_OK
&& file.section) {
chmd->extract(chmd, &file, "index.html");
}
fast_find returns MSPACK_ERR_OK with file.section set to NULL when the file doesn’t exist.
Tips#
- Never use
f->filenamedirectly as an output path: a hostile CHM can contain..components. Sanitize it first. - Pass your own
struct mspack_systemtomspack_create_chm_decompressorto read the CHM from memory or write extracted files to your own streams. - The same library extracts CAB archives (
mspack_create_cab_decompressor) and other Microsoft formats.