std/archive

ZIP and TAR, gzip-compressed TAR among them: read, written, made of a directory, and extracted without leaving the directory they are extracted into.

import "std/archive"

ArchiveEntry[] entries = read_archive("release.zip")?
for e in entries do
    print(e.name, e.kind, len(e.data))
end
extract(entries, "out")?

write_archive("notes.tar.gz", archive_directory("notes", "notes/")?)?

An ArchiveEntry has its name, with / between its parts (a directory's ends with /); its data; its kind, one of "file", "directory", "symlink", and "hardlink"; modified, in seconds since 1970 UTC, 0 when not known; mode, the permissions, 0 for the usual (0o644 for files and 0o755 for directories); and link, what a link points to.

Function Result
read_zip(data, limit), write_zip(entries, level) ZIP: entries stored or deflated at level, stored when deflating does not make them smaller; ZIP64 for archives past 4 GiB or 65,535 entries
read_tar(data), write_tar(entries) TAR: POSIX ustar, with PAX headers for names and links that ustar cannot hold or that are not ASCII, sizes of 8 GiB or more, and times before 1970
read_tar_gz(data, limit), write_tar_gz(entries, level) gzip-compressed TAR
read_archive_bytes(data, limit) any of them, known by how the data starts; a ZIP after other data, as a self-extracting program has it, too
read_archive(path, limit), write_archive(path, entries, level) a file: written as its extension says, .zip, .tar, .tar.gz, or .tgz
archive_directory(directory, prefix) the entries of everything below a directory, named from it after prefix; links are kept as links
read_ar(data), write_ar(entries) ar, as Debian packages and static libraries are made of: files only, with GNU's and BSD's long names read; an ERROR of kind "invalid_ar" for data that is not ar
extract(entries, directory) writes the entries under the directory and gives how many

Reading checks what the formats can check: ZIP's CRC-32s and sizes, and TAR's header checksums. A ZIP entry's name without the UTF-8 mark is read as code page 437, as ZIP has it, unless Info-ZIP's Unicode path field gives it; its time is the extended timestamp when the archive has one, or else MS-DOS's local time, taken as UTC; and names that Windows tools wrote with \ between their parts are read with /. GNU tar's long names and its numbers in base 256 are read, and devices and FIFOs passed over. limit stops archives whose entries would come to more bytes, with an ERROR of kind "too_large".

The errors are of kind "invalid_zip" and "invalid_tar" for damaged archives; "unsupported" for encrypted ZIP entries, compression other than store and deflate, and archives on several disks; and "invalid_argument" for writing a name that is absolute or has .., or a hard link into ZIP, which has none.

extract keeps every path in the directory: before writing anything, it refuses an entry with an absolute name, a name with .. that goes out, a symbolic link that points out, and a hard link to something outside, with an ERROR of kind "unsafe_path"; while writing, it refuses to write a file through a link that is already there. Links are made after the files and directories. Times and permissions are kept, without set-user-ID and set-group-ID bits. A hard link is made as one, or as a copy where the system makes none.