ZIP archive reading and extraction
The archo module provides ZIP archive handling built on zlib's minizip API. We use it wherever we need to unpack downloaded archives to disk; it is a direct dependency of pacm, which calls it to extract package files after each download completes.
The module exposes a single class, icy::archo::ZipFile, which wraps a unzFile handle and offers two levels of access: a high-level extract() call that unpacks everything to a directory, and a lower-level cursor API for iterating entries selectively. Every extraction path validates entry names against path-traversal attacks before any file is written.
Dependencies: base, zlib (minizip)
The common case: open an archive and write all of its contents to a destination directory. The directory is created automatically if it does not exist.
#include <icy/archo/zipfile.h>
icy::archo::ZipFile zip("/path/to/package.zip");
zip.extract("/opt/myapp/plugins");
// All entries are now on disk under /opt/myapp/plugins/.The two-step form is identical; construct first, then open:
icy::archo::ZipFile zip;
zip.open("/path/to/package.zip");
zip.extract("/opt/myapp/plugins");After open() (or construction with a path), the info vector is populated with a FileInfo record for every entry in the archive. We can inspect sizes or filter the list before touching disk.
icy::archo::ZipFile zip;
zip.open("/path/to/package.zip");
for (const auto& entry : zip.info) {
std::cout << entry.path
<< " compressed=" << entry.compressedSize
<< " uncompressed=" << entry.uncompressedSize << "\n";
}
zip.extract("/opt/myapp/plugins");Use goToFirstFile() and goToNextFile() to walk the archive and call extractCurrentFile() only for the entries we want.
icy::archo::ZipFile zip("/path/to/archive.zip");
if (zip.goToFirstFile()) {
do {
std::string name = zip.currentFileName();
if (name.ends_with(".so") || name.ends_with(".dll")) {
zip.extractCurrentFile("/opt/myapp/plugins");
}
} while (zip.goToNextFile());
}extractCurrentFile() handles openCurrentFile() / closeCurrentFile() internally; we only need to call those directly if we are implementing custom read logic at a lower level.
open() and extract() throw std::runtime_error on failure. extractCurrentFile() accepts an optional whiny parameter; when false it returns a boolean instead of throwing, which is useful when partial failures are acceptable.
try {
icy::archo::ZipFile zip("/path/to/archive.zip");
zip.extract("/destination");
} catch (const std::runtime_error& e) {
std::cerr << "extraction failed: " << e.what() << "\n";
}
// Or, tolerate per-entry failures:
if (!zip.extractCurrentFile("/destination", /*whiny=*/false)) {
// log and continue
}icy::archo::ZipFile zip;
// zip.opened() == false
zip.open("/path/to/archive.zip");
// zip.opened() == true
zip.close();
// zip.opened() == falseThe destructor closes the handle automatically, so explicit close() calls are only needed when reusing the object for a second archive.
extractCurrentFile() validates every entry name before writing. It rejects:
..)/ and \) in unexpected positionsAny entry that fails validation throws std::runtime_error. The high-level extract() routes every entry through extractCurrentFile(), so the validation is always applied. We never write a file to a location outside the destination directory.
The archo module has no configuration options. It is built automatically as a dependency of the pacm module.