CHMLib
| Language | C (usable from C++) |
|---|---|
| License | LGPL-2.1-or-later |
| Repository | github.com/jedwing/CHMLib |
| Documentation | comments in chm_lib.h and the example programs in src/ |
| Depends on | nothing (includes its own LZX decoder) |
| Read / write | yes / no |
CHMLib (chmlib) reads the ITSS container used by CHM files: it looks up entries by path, reads any byte range of an entry (decompressing LZX as needed, with a block cache), and enumerates entries. It’s the library behind xCHM, KchmViewer, pychm and the extract_chmLib and chm_http tools.
The API has 6 functions and is stable. Development stopped with version 0.40 (2009); Linux distributions still ship it with security patches. For hostile input consider libmspack, which is maintained.
Installation#
# Debian / Ubuntu
sudo apt install libchm-dev
# Fedora
sudo dnf install chmlib-devel
# macOS
brew install chmlib
Compile with cc app.c -lchm.
Basic usage: list files and read a page#
#include <chm_lib.h>
#include <stdio.h>
#include <stdlib.h>
static int print_entry(struct chmFile *h, struct chmUnitInfo *ui, void *ctx) {
(void)h; (void)ctx;
printf("%10llu %s\n", (unsigned long long)ui->length, ui->path);
return CHM_ENUMERATOR_CONTINUE;
}
int main(void) {
struct chmFile *h = chm_open("help.chm");
if (!h) {
fprintf(stderr, "cannot open file\n");
return 1;
}
/* user files only, no directories or internal ::DataSpace files */
chm_enumerate(h, CHM_ENUMERATE_NORMAL | CHM_ENUMERATE_FILES, print_entry, NULL);
struct chmUnitInfo ui;
if (chm_resolve_object(h, "/index.html", &ui) == CHM_RESOLVE_SUCCESS) {
unsigned char *buf = malloc(ui.length + 1);
LONGINT64 n = chm_retrieve_object(h, &ui, buf, 0, ui.length);
buf[n > 0 ? n : 0] = 0;
printf("%s\n", buf);
free(buf);
}
chm_close(h);
return 0;
}
Paths start with / and are matched case-insensitively. chm_retrieve_object returns the number of bytes read.
How to extract all files from a CHM#
#include <chm_lib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int extract(struct chmFile *h, struct chmUnitInfo *ui, void *ctx) {
const char *outdir = ctx;
if (ui->length == 0 || ui->path[0] != '/')
return CHM_ENUMERATOR_CONTINUE;
char dest[CHM_MAX_PATHLEN + 256];
snprintf(dest, sizeof dest, "%s%s", outdir, ui->path);
/* create parent directories of dest here (mkdir -p) */
unsigned char *buf = malloc(ui->length);
if (buf && chm_retrieve_object(h, ui, buf, 0, ui->length) == (LONGINT64)ui->length) {
FILE *f = fopen(dest, "wb");
if (f) {
fwrite(buf, 1, ui->length, f);
fclose(f);
}
}
free(buf);
return CHM_ENUMERATOR_CONTINUE;
}
/* chm_enumerate(h, CHM_ENUMERATE_NORMAL | CHM_ENUMERATE_FILES, extract, "out"); */
Return CHM_ENUMERATOR_SUCCESS from the callback to stop early, CHM_ENUMERATOR_FAILURE to abort with an error.
How to list one folder#
chm_enumerate_dir(h, "/images/", CHM_ENUMERATE_FILES, print_entry, NULL);
How to find the table of contents#
The .hhc file has no fixed name. Enumerate entries and pick the one ending in .hhc (the index ends in .hhk), or read the /#SYSTEM entry, which stores the names. The .hhc file is HTML with <object type="text/sitemap"> elements; each has <param name="Name"> (the title) and <param name="Local"> (the page path).
Tips#
ui->spacetells whether an entry is stored uncompressed (CHM_UNCOMPRESSED) or in the LZX section (CHM_COMPRESSED).chm_set_param(h, CHM_PARAM_MAX_BLOCKS_CACHED, n)raises the decompressed block cache for faster random access in large files.- Non-ASCII file names are returned as raw UTF-8 bytes.