packkit/seven_z
7z archive — minimal pure-Gleam reader.
The reader handles the common case produced by 7z a on a small
payload: a single packed stream wrapped in a single folder that
uses one coder. Recognised single-coder ids:
- LZMA2 (
0x21) - raw LZMA (
0x03 0x01 0x01) - Copy (
0x00) — identity passthrough - Deflate (
0x04 0x01 0x08) — raw DEFLATE, delegated to packkit/deflate - BZip2 (
0x04 0x02 0x02) — full BZh stream, delegated to packkit/bzip2 Multiple files packed into that folder are supported when the archive carries aSubStreamsInfoblock — the parser reads the per-substream sizes fromkSize(0x09), derives the final size from the folder’s total, and splits the decoded stream accordingly. Multi-coder folders (BCJ filters in front of LZMA2, AES decryption in front of LZMA2), AES-256-CBC encryption with p7zip’s SHA-256-based key derivation (06 F1 07 01), and multiple folders are supported. Header encryption (-mhe=on, encrypts the next-header itself) is intentionally rejected withArchiveNotImplemented. AES-encrypted archives require thedecode_with_password/decode_with_password_and_limitsentry points. The encoder is unaffected — it still emits a single LZMA-coded folder regardless of which coders the decoder accepts.
Types
Coder selection for the single-folder 7z encoder. Mirrors the
7z a -m0=<method> CLI option: every entry’s body is concatenated,
then the whole buffer is fed through the chosen coder and emitted
as one folder. The decoder side accepts the same set plus extra
coder ids it doesn’t yet have an encoder for (LZMA2, BCJ family,
Delta).
pub opaque type Method
Values
pub fn bzip2() -> Method
Method constructor: full bzip2 stream (with BZh magic and CRCs).
The concatenated entry bodies go through packkit/bzip2.encode.
Equivalent to 7z a -m0=BZip2.
pub fn copy() -> Method
Method constructor: identity (no compression). Equivalent to
7z a -m0=Copy. The packed bytes stored in the folder are the
concatenated entry bodies verbatim.
pub fn decode(
bytes bytes: BitArray,
) -> Result(archive.Archive, error.ArchiveError)
Decode a 7z byte stream using the default limits.
pub fn decode_with_limits(
bytes bytes: BitArray,
limits limits: limit.Limits,
) -> Result(archive.Archive, error.ArchiveError)
Decode a 7z byte stream using explicit limits. Enforces
max_input_bytes at entry, max_output_bytes against the
declared unpack size before invoking the LZMA/LZMA2 decoder,
and max_members / max_entry_depth while materialising the
logical entry list.
pub fn decode_with_password(
bytes bytes: BitArray,
password password: String,
) -> Result(archive.Archive, error.ArchiveError)
Decode a 7z byte stream that contains an AES-256 encrypted folder.
password is applied to every AES-coded folder via the SHA-256
key-derivation routine p7zip uses (numCyclesPower rounds of
sha256(salt || utf16le(password) || u64_le(counter))); folders
without an AES coder decode unchanged. A wrong password produces
garbage plaintext that the downstream coder (typically LZMA2)
rejects with the usual typed ArchiveInvalid error — there’s no
authentication tag on the AES layer.
pub fn decode_with_password_and_limits(
bytes bytes: BitArray,
password password: String,
limits limits: limit.Limits,
) -> Result(archive.Archive, error.ArchiveError)
Same as decode_with_password but with explicit limits.
pub fn deflate() -> Method
Method constructor: raw DEFLATE (no zlib / gzip wrapper). The
concatenated entry bodies go through packkit/deflate.encode.
Equivalent to 7z a -m0=Deflate.
pub fn encode(
archive archive_value: archive.Archive,
) -> Result(BitArray, error.ArchiveError)
Encode a logical archive to a 7z byte stream.
The encoder produces a single-folder, single-coder archive that
uses a raw LZMA1 coder for the packed stream. All file bodies are
concatenated into one logical substream which is then fed to
packkit/internal/lzma.encode_literal_only; if the archive holds
more than one entry, the per-substream sizes are emitted via
SubStreamsInfo so the standard decoder can split the decompressed
bytes back into individual entries.
Restrictions: only File entries are accepted (no directories,
symlinks, or hardlinks — these would require the EmptyStream /
Attribute / WinAttributes blocks the reader does not yet
validate). Empty archives are rejected because the 7z format
requires MainStreamsInfo to be present once a Header block
exists.
pub fn encode_with_method(
archive archive_value: archive.Archive,
method method: Method,
) -> Result(BitArray, error.ArchiveError)
Same as encode/1 but lets the caller pick the coder. All four
methods produce a single-folder, single-coder archive — Copy is
just the raw bytes; Deflate / BZip2 / LZMA dispatch to the
corresponding packkit codec. Cross-method round-trips work
because the decoder accepts each of these coder ids.
pub fn encode_with_password(
archive archive_value: archive.Archive,
password password: String,
) -> Result(BitArray, error.ArchiveError)
Encode a logical archive to a 7z byte stream with AES-256-CBC
payload encryption (coder id 06 F1 07 01). Plumbed into the
folder as a 2-coder chain [AES, <method>] so the existing
decode_with_password path reads it back verbatim.
The encoder uses empty salt and a 16-byte all-zero IV for determinism — without an FFI into a platform CSPRNG there is no good source of randomness in pure Gleam. This is fine for archives that go to a trusted recipient over an authenticated channel, but it is NOT a substitute for randomised encryption — two archives encrypted with the same password produce identical ciphertext blocks for identical plaintext prefixes, which leaks information about the payload. If you need randomised encryption, encrypt the payload yourself before passing it in or pre-generate a 16-byte IV from a CSPRNG and feed it back through a future BYO-IV API.
The inner coder defaults to LZMA — matching what 7z a -p<pw>
emits — and the key derivation runs the canonical 2^19 = 524288
SHA-256 iterations, which on the JS target is slow (multiple
seconds per archive).
pub fn encode_with_password_and_header_encryption(
archive archive_value: archive.Archive,
password password: String,
) -> Result(BitArray, error.ArchiveError)
Encode an archive with both the payload AND the next-header bytes
AES-encrypted — the on-disk equivalent of 7z a -p<pw> -mhe=on.
The signature header points at an “encoded next header” (NID 0x17)
whose StreamsInfo block tells the decoder where the AES-encrypted
next-header pack lives; the next header itself is plain bytes
AES-CBC-encrypted with the user’s password (no inner compression
— the header is small enough that LZMA isn’t worth the dispatch
cost).
The same determinism caveat as encode_with_password applies: salt
is empty and IV is all zeros.
pub fn encode_with_password_and_header_encryption_and_method(
archive archive_value: archive.Archive,
password password: String,
method method: Method,
) -> Result(BitArray, error.ArchiveError)
Like encode_with_password_and_header_encryption but lets the
caller pick the inner coder for the payload. The header is
always wrapped in just AES (no inner method).
pub fn encode_with_password_and_method(
archive archive_value: archive.Archive,
password password: String,
method method: Method,
) -> Result(BitArray, error.ArchiveError)
Same as encode_with_password but lets the caller pick the inner
coder (Copy / Deflate / BZip2 / LZMA). Salt + IV are still empty /
all-zero — see encode_with_password for the determinism caveat.
pub fn lzma() -> Method
Default Method: raw LZMA1 with the historical 7z a defaults
(lc=3 / lp=0 / pb=2, 64 KiB dictionary). Equivalent to passing
seven_z.lzma() to encode_with_method.