Specfork

@specfork/archiver

Desktop file archiver for Windows, macOS and Linux. Create, extract, list, test and update 7z, zip, tar, gzip, bzip2 and xz archives, with AES-256 and safe extraction.

desktopfiles Version 0.1.1 108 requirements Licence CC-BY-4.0 Specfork/archiver

Archiver

A desktop program that packs files and folders into archives and unpacks them again. It creates and updates 7z, zip, tar, gzip, bzip2 and xz archives, reads many more formats through a codec contract, encrypts with AES-256 (optionally hiding file names), splits archives into volumes, tests integrity, and plugs into the system file manager. Extraction is safe by default: nothing is written outside the chosen folder. Behaviour derived from 7-Zip's open-source code (LGPL-2.1-or-later), written fresh and stack-free. Not affiliated with or endorsed by Igor Pavlov. The name is used only to credit the source.

Area Prefix Covers
formats.md FMT Format registry, capabilities, methods, levels, codec contract, detection, metadata, volumes
create.md CRT Creating and updating archives: inputs, naming, paths, solid blocks, update modes, links, delete-after
extract.md XTR Extraction modes, destination, overwrite prompts, volumes, free space, metadata restore
safety.md SAFE Path traversal, absolute paths, links, special files, reserved names, permissions
names.md NAM Unicode names, legacy code pages, separators, collisions, length limits
encryption.md ENC Passwords, AES-256, header encryption, legacy zip encryption, wrong-password handling
browse.md BRW Archive browser: listing, navigation, open, add, delete, rename, properties
integrity.md INT Integrity test, error kinds, checksums of files
jobs.md JOB Progress, pause, cancel, outcomes, messages, temporary files, performance
integration.md SHL File-manager menu actions, drag and drop, file associations, command line, settings

Single user, local only: no account, no network, no telemetry. Every rule runs on one device. Sizes use binary units (1 KiB = 1024 B); times are local; enum values are snake_case. Keyboard shortcuts are defaults; a build may follow its platform's conventions instead.

Archive browser

Status: Draft · Prefix: BRW

Purpose

Looking inside an archive like a folder, and changing it where the format allows.

Requirements

ID Requirement Verified by
BRW-1 Opening an archive shows its top level as a folder; folders implied by item paths but not stored as entries are shown too. zip with only a/b.txt → folder a listed
BRW-2 Columns: name, size, packed_size, modified, plus optional created, accessed, attributes, encrypted, crc32, method, block, host_os; a column the format lacks is blank. Folder size = sum of its contents. folder with 2 × 1 KiB files → 2 KiB
BRW-3 Any column sorts ascending/descending on click and the choice persists. Names sort case-insensitively with digit runs compared as numbers, folders first. f2, f10, F1 → F1, f2, f10
BRW-4 Navigation: open a folder (Enter or double-click), go up (Backspace), a path bar archive › folder › … with clickable parts, and back/forward history within the archive. Enter on a/, Backspace → root
BRW-5 A filter box narrows the current folder by case-insensitive name substring as the user types. type rep → only names containing rep
BRW-6 Opening a file item extracts it to a private temporary folder and opens it with the OS default app (after SAFE-12 where it applies); a nested archive opens inside the browser instead. open notes.txt → default editor shows it
BRW-7 When an item opened per BRW-6 changes on disk while the archive is open, the user is asked whether to write it back (formats with update); yes runs an update job. edit and save → prompt; yes → item updated
BRW-8 Dropping files onto the browser, or the add command, runs an update job (add_replace, CRT-8) into the current folder. Dragging items out to a file-manager folder extracts them there per XTR. drop x.txt in docs/ → docs/x.txt; drag a.txt to desktop → file there
BRW-9 Delete (Delete key) asks for confirmation with the item count, says the change is permanent, then rewrites the archive per CRT-10. delete 2, confirm → 2 fewer items
BRW-10 Rename (F2) and new folder change the archive in place; a name already in that folder is refused. For formats without update, and split archives, add, delete, rename and new folder are disabled and the window says read-only. rename to existing name → refused; open .iso → delete disabled
BRW-11 Properties: format, physical size, unpacked total, file and folder counts, methods, solid yes/no and block count, encrypt_names, volume count, offset when embedded in a larger file, and any open warnings. solid 7z → 1 block shown
BRW-12 An archive of 100,000 items shows its first screen within 3 s and scrolls without visible stalls. manual: 100,000-item zip on a mid-range laptop

Out of scope

  • A general two-pane file manager for the disk; editing archive comments.

Decisions

  • The browser is the only editing surface; the file-manager menu (SHL) only creates, extracts, tests and hashes.
  • Write-back of opened items is always asked, never automatic.

Create and update

Status: Draft · Prefix: CRT

Purpose

Packing files and folders into a new archive, or changing an existing one.

Requirements

ID Requirement Verified by
CRT-1 A create job takes inputs (one or more files or folders) and options {archive_path, format, level, method, dictionary_size, word_size, solid_block, threads, memory_limit, update_mode, path_mode, volume_sizes[], password, encrypt_names, excludes[], links, delete_after, test_after}; unset options take this spec's defaults. only inputs given → 7z, level 5, lzma2
CRT-2 Default archive_path: the first input's folder + a base name (one file: its name without the last extension; one folder: its name; several items: their parent folder's name) + the format extension. A typed name without the extension gets it; changing format swaps it. report.txt → report.7z; a.txt+b.txt in docs/ → docs/docs.7z; switch to zip → docs.zip
CRT-3 Folders are added recursively with every file, hidden ones included, and with empty folders as folder entries. folder with empty sub/ → archive lists sub/
CRT-4 path_mode: relative (default; paths start at each input's own name), full (from the volume root, without drive letter or leading separator), absolute (with root; 7z and tar only). /home/u/docs/a.txt: relative → a.txt; full → home/u/docs/a.txt
CRT-5 excludes: wildcards (* any run, ? one character) matched against each name, or against the relative path when the pattern has a separator; a matching folder is skipped with its contents. The archive itself, its volumes and its temporary file are never added. exclude *.tmp → no .tmp; pack x into x/x.7z → no x.7z inside
CRT-6 solid_block (7z, xz): off, 1 MiB–64 GiB, or full; default from level (≥ 64 MiB at level 5). Files in a block are ordered by extension, then name. The dialog's help says that reading or replacing one file touches its whole block. 1,000 small text files → solid archive smaller than off
CRT-7 If archive_path exists in the same format, the job updates it per update_mode; another format fails with format_mismatch; a split archive fails with volumes_read_only. Nothing changes on failure. add to a.7z as zip → format_mismatch; add to a.7z.001 → volumes_read_only
CRT-8 update_mode: add_replace (default: add all, replace same paths), update_add (add new, replace only where the disk copy is newer), freshen (replace existing paths where newer, add nothing), sync (update_add plus remove archive items absent from the inputs). "Newer" compares modified times at the precision both sides keep; equal is not newer. freshen with a new file → not added; same file update_add → not rewritten
CRT-9 sync that would remove ≥ 1 item shows the count and the first 20 paths and needs explicit confirmation; declining cancels the whole job. sync removing 3 → confirm lists 3; No → archive unchanged
CRT-10 Updating writes a new temporary archive beside the old one and replaces it only on success; on error or cancel the original stays byte-identical. cancel at 50% → original hash unchanged
CRT-11 volume_sizes: sizes as number + unit B, K, M, G (binary); each volume takes the next size and the last repeats; minimum 64 KiB. Presets: 10M, 100M, 650M, 700M, 4092M, 4480M. 10M on 25 MiB → volumes 10, 10, 5 MiB
CRT-12 Unreadable or locked inputs are skipped, listed in the job messages, and the job ends completed_with_warnings (JOB-5). 1 locked file of 5 → archive has 4, warning names it
CRT-13 links: store_link (default where the format has links; the link itself is stored) or follow (store target content, loops stored once with a warning); formats without links always follow. tar stores the second and later paths of one hard-linked file as hard-link entries. link to a.txt in tar → link entry; 2 hard links of 1 MiB → tar ≈ 1 MiB
CRT-14 delete_after (default off) removes inputs only after the archive is complete and, when test_after (default off) is on, tested ok (INT-1); skipped inputs are never removed. A failed test ends the job failed and keeps the archive. 1 input skipped → that input still on disk
CRT-15 Turning delete_after on needs a confirmation naming the item count and that removal is permanent where no trash exists; on the command line it needs an explicit switch. Inputs go to the system trash when one exists. enable → confirm shows "5 items" before start
CRT-16 threads: 1 to logical CPU count, default all; memory_limit default 80% of RAM. While the packing estimate exceeds the limit, start is blocked showing both numbers. dictionary 1.5 GiB on 4 GiB RAM → start blocked

Out of scope

  • Archive comments, rename-on-add rules, scheduled backups.

Decisions

  • A single file's base name drops its last extension: photo.jpg → photo.7z.
  • delete_after prefers the trash; the source deletes permanently.
  • Minimum volume size 64 KiB: smaller slices mostly create thousands of files by mistake.

Encryption

Status: Draft · Prefix: ENC

Purpose

Password-protecting archive contents and, in 7z, the list of names.

Requirements

ID Requirement Verified by
ENC-1 7z encryption is AES-256 in CBC mode; the key derives from the UTF-16 password and a random salt by iterated SHA-256 (2^19 rounds); every archive gets a fresh random IV. same files and password twice → different bytes, both open
ENC-2 zip encryption: aes256 (default; the common AES zip extension) or legacy (traditional zip encryption), which shows a weakness warning before start. choose legacy → warning
ENC-3 Password: 1–1024 characters, any Unicode; empty means none. Masked by default with a show_password toggle; while masked, a second entry must match before start. Formats without encrypt (FMT-1) disable the fields. masked entries differ → start blocked
ENC-4 encrypt_names (7z only; default off) also encrypts the header: without the password nobody sees names, sizes or count, and opening asks for the password before listing. With zip, the dialog notes that names and sizes stay visible. header-encrypted 7z → prompt before listing; zip + password → note shown
ENC-5 A wrong password gives wrong_password (when the format can tell) or data_error with a hint that the password may be wrong; nothing partial stays (XTR-8); the prompt returns, up to 3 attempts per job. wrong password → error, prompt again, nothing written
ENC-6 Updating an encrypted 7z: new items use this job's password; kept items stay as they are; a header-encrypted archive needs its password before any update. add to header-encrypted archive → password asked first
ENC-7 Encrypted items carry a marker in the browser. Zip archives may mix plain and encrypted items; the password is asked once per job, only when an encrypted item is read, and again only for an item it fails on. mixed zip → plain item extracts with no prompt; 100 encrypted items → one prompt
ENC-8 Passwords live only in memory for one job; they never reach settings, history, logs or temporary files, and are wiped when the job ends. search settings and logs after job → absent

Out of scope

  • Public-key encryption, key files, hardware tokens, saved passwords, password recovery.

Decisions

  • No saved passwords (ENC-8): an archive password stored on the same disk adds little and leaks easily.
  • aes256 is the zip default; legacy stays for old receivers only.

Extract

Status: Draft · Prefix: XTR

Purpose

Unpacking all or part of an archive to a folder, and what happens when files already exist.

Requirements

ID Requirement Verified by
XTR-1 An extract job takes an archive (or any volume of a split one), a selection (all, or browser items, kept relative to the folder shown) and options {destination, path_mode, overwrite, password, flatten_single_root, keep_broken, restore_times, origin_mark, extract_links, allow_unsafe_links}. in src/, extract lib/a.c → <dest>/lib/a.c
XTR-2 Default destination: the archive's folder + its base name with every archive extension stripped (a.tar.gz → a, a.7z.001 → a). Missing folders, parents included, are created. photos.zip in Downloads/ → Downloads/photos/
XTR-3 flatten_single_root (default on): when the top level holds exactly one folder named like the destination's last component, its contents go straight into the destination. proj.zip with only proj/… → proj/file, not proj/proj/file
XTR-4 path_mode: full (default; keep stored folders), none (all files into the destination), absolute (keep a stored root; needs a confirmation per job and stays subject to SAFE-3). none on a/b/c.txt → <dest>/c.txt
XTR-5 overwrite: ask (default), overwrite, skip, rename_new, rename_existing. skip with existing file → untouched, counted skipped
XTR-6 The ask prompt shows both files' name, size and modified time, and offers yes, yes_to_all, no, no_to_all, rename, cancel; *_to_all lasts for this job only. no_to_all → no more prompts, rest skipped
XTR-7 Renaming picks the first free name (1).ext, name (2).ext, …; rename_existing renames the file on disk and writes the new one under the original name. existing a.txt, a (1).txt → new file a (2).txt
XTR-8 Items are written under a temporary name and renamed on success. On error or cancel the partial file is removed unless keep_broken (default off); an item failing its checksum is reported crc_failed and kept only with keep_broken. cancel mid-file → no partial file; flipped byte → error, file absent
XTR-9 Split archives: opening any volume finds siblings by name; a missing volume pauses the job with a prompt to locate it or cancel, naming the expected file. delete .002 → prompt names a.7z.002
XTR-10 When the known unpacked total exceeds free space on the destination volume, a warning shows both numbers before writing; the user continues or cancels. 2 GiB archive, 1 GiB free → warning
XTR-11 restore_times (default on) sets modified time, plus created/accessed when stored and supported; folder times are set after their contents. POSIX modes (minus SAFE-8 bits) apply on POSIX systems, Windows attributes on Windows. extracted mtime = stored mtime
XTR-12 origin_mark (all, documents_only, off; default all): when the archive carries the OS's downloaded-from-the-internet mark, extracted files get it too; documents_only limits it to office documents and scripts. marked archive → extracted .docx marked
XTR-13 Several archives selected together extract each into its own default destination, one after another, as one job with a combined summary. 3 zips → 3 folders, one summary

Out of scope

  • Restoring owners, ACLs or extended attributes other than the origin mark.
  • Extracting to remote or cloud locations.

Decisions

  • Rename uses name (n).ext, the convention most file managers use; the source uses another suffix.
  • Write-then-rename (XTR-8) means a crash never leaves a half file under the real name.
  • flatten_single_root is a default, not a menu entry, so extracting to a folder never doubles it.

Formats

Status: Draft · Prefix: FMT

Purpose

Which archive formats exist, what each can do, and the codec contract that adds more.

Requirements

ID Requirement Verified by
FMT-1 Every format is a registry entry {id, extensions[], signatures[], caps}; caps is a set of create, extract, update, multi_file, solid, encrypt, encrypt_names, links. Every command and dialog offers only what caps allows. format tar chosen → password field disabled
FMT-2 Read-write formats and caps: 7z {create, extract, update, multi_file, solid, encrypt, encrypt_names, links}; zip {create, extract, update, multi_file, encrypt, links}; tar {create, extract, update, multi_file, links}; gzip, bzip2, xz {create, extract}. Output opens in other common tools for the same format. manual: sample 7z, zip, tar.gz open in two other archivers
FMT-3 gzip, bzip2 and xz hold one stream: packing one file makes <file>.gz/.bz2/.xz; packing several items or a folder offers only tar.gz, tar.bz2, tar.xz, built in one pass. Opening .tar.gz, .tgz, .tar.bz2, .tbz2, .tar.xz, .txz goes straight to the tar listing. 2 files + gzip → only .tar.gz offered; open a.tgz → tar entries
FMT-4 Read-only formats (caps {extract, multi_file}): rar, cab, iso, udf, wim, cpio, rpm, ar/deb, dmg, xar, lzh, arj, chm, msi, nsis, squashfs, cramfs, vhd, vhdx, vmdk, vdi, qcow2, fat, ntfs, ext, hfs, apfs; single-stream read-only: lzma, z, zstd. Each is optional; the about view lists those built in. open .iso → list and extract work, add disabled
FMT-5 Codec contract: a read-only format is a module exposing probe(first_bytes) → confidence 0..100, open(stream, volume_provider, password_provider) → archive, archive.items() → [{path, is_dir, size, packed_size?, mtime?, attributes?, crc32?, encrypted, link_target?}], archive.read(item) → byte stream, archive.properties() → map. A codec reports errors only as INT-3 kinds and never writes to disk, so every SAFE rule applies to it. stub codec with items a, ../x → a extracted, ../x blocked
FMT-6 Detection: the signature (at the start, or at the end for formats that keep it there) wins over the extension; on disagreement the archive opens as the detected format with a warning naming it; "open as" forces a format. No match → error unknown_format, never an empty archive. photo.zip that is 7z → opens as 7z + warning; random bytes → unknown_format
FMT-7 Methods: 7z: lzma2 (default), lzma, ppmd, bzip2, deflate, deflate64, copy; zip: deflate (default), deflate64, bzip2, lzma, ppmd, store; gzip: deflate; bzip2: bzip2; xz: lzma2; tar: no compression, layout pax (default) or gnu. 7z defaults → method lzma2
FMT-8 Levels: 0 store, 1 fastest, 3 fast, 5 normal (default), 7 maximum, 9 ultra. Allowed: 7z 0–9; zip 0, 1, 3, 5, 7, 9; gzip 1, 5, 7, 9; bzip2 1, 3, 5, 7, 9; xz 1–9; tar 0. Another level is rejected before work starts. Level 0 stores data uncompressed. gzip level 3 → rejected, nothing written; 10 MiB random at level 0 → archive ≥ 10 MiB
FMT-9 Advanced parameters dictionary_size, word_size, solid_block (CRT-6), threads default from the level and may be overridden; the dialog shows estimated memory for packing and for unpacking. dictionary 16 → 64 MiB → both estimates rise
FMT-10 zip switches to 64-bit fields when any item or the archive exceeds 4 GiB − 1 B or holds more than 65,535 items; 7z and tar (pax) hold items up to 2^63 B. 5 GiB file zipped → extracts with equal hash
FMT-11 Times: modified time is stored in every format; 7z adds created and accessed time when store_times includes them (default: modified only); zip stores its 2-second local time plus an exact UTC time. zip file modified 10:00:01 → extracted 10:00:01
FMT-12 Attributes: POSIX mode is stored in tar, zip, 7z; Windows read_only, hidden, system, archive in zip, 7z; each is restored only where the OS has it (XTR-11). mode 0755 packed on Linux → 0755 on macOS
FMT-13 Volumes: any created archive may be split into raw slices <archive>.001, .002, … (3 digits; 4 past 999); concatenating them gives the unsplit archive. 25 MiB archive, 10 MiB volumes → 3 files; concatenation hash = unsplit hash

Out of scope

  • Creating any read-only format, including rar and wim.
  • Self-extracting executables; format-native zip spanning (.z01).
  • Executable pre-filters and other per-method tuning as user options; benchmarks.

Decisions

  • RAR is extract-only and only through the codec contract (FMT-5). The source's RAR decoder ships under a separate unRAR licence that forbids using it to re-create the RAR compressor; this spec was written without reading that code. Implementers who bundle a RAR codec must honour that licence or use a decoder under a compatible one, and may simply omit rar.
  • Source licence: most of 7-Zip is LGPL-2.1-or-later; two decoders are BSD-3-Clause and one hash is BSD-2-Clause. derived_from.license records the main licence.
  • tar defaults to pax (the source defaults to gnu): pax stores UTF-8 names, long paths and exact times portably.
  • The read-only list is a menu, not a mandate: an implementation is complete with the six read-write formats plus any codecs it picks.

Desktop integration

Status: Draft · Prefix: SHL

Purpose

Reaching the archiver from the system file manager and the command line, and its settings.

Requirements

ID Requirement Verified by
SHL-1 File-manager menu actions: open_archive, extract_with_options, extract_here, extract_to_folder, test_archive (on archives); add_with_options, add_to_7z, add_to_zip (on any items); checksum (on files and folders). Each OS's supported extension mechanism is used. right-click a zip → extract actions present
SHL-2 extract_here writes into the archive's folder: a single top item goes there directly, several go into a folder named per XTR-2. extract_to_folder always uses XTR-2 and shows the folder name in the entry. zip with 3 top files → name/ created; menu shows photos/
SHL-3 add_to_7z and add_to_zip create the archive at the default path (CRT-2) with default options and no dialog; an existing file of that name is updated with add_replace. 2 files selected → <folder>.7z created
SHL-4 Archive actions appear only on files whose extension or signature matches an extract format; with several archives selected they apply to each (XTR-13). right-click .txt → no extract actions
SHL-5 menu_actions (which actions show; default all) and menu_cascaded (in one submenu; default on) apply at once. turn off checksum → gone from menu
SHL-6 File associations are opt-in per extension; none are claimed without the user's choice. Dropping files on the app opens the add dialog; dropping an archive opens the browser. fresh install → .zip still opens in previous app
SHL-7 Command line: a <archive> <inputs…> create/update, x <archive> [-o <dir>] extract with paths, e extract flat, t test, l list, d <archive> <paths…> delete items, h <files…> checksums; switches cover every option of CRT-1 and XTR-1. Prompts become errors unless -y (assume yes) is given or stdin is a terminal. x a.zip -o out → files in out/
SHL-8 l prints one line per item (modified, attributes, size, packed size, path) and totals; l -json prints a JSON array with the fields of BRW-2. l -json a.zip → valid JSON array
SHL-9 Exit codes: 0 succeeded, 1 completed with warnings, 2 failed or completed with errors, 7 bad command line, 8 out of memory, 255 cancelled. test corrupt archive → 2
SHL-10 Settings, stored locally per user: default_format (7z), default_level (5), legacy_code_page, temp_folder, keep_history (on: last 20 archive paths and last options per format), keep_job_view_open (off), extract_links (on), origin_mark (all), menu_actions, menu_cascaded, language (OS language). "Clear history" empties the remembered paths and options after a confirmation. change default format → next add dialog preselects it; clear → no previous paths
SHL-11 The app works fully offline and sends nothing over the network: no update checks, analytics or crash uploads. manual: run every job type with network blocked, watch traffic

Out of scope

  • Email-after-compress, cloud upload, installers.

Decisions

  • No @specfork/privacy requirement: the app keeps no personal records, only settings and path history, which SHL-10 clears. Privacy's local mode would add a ZIP export of nothing useful.
  • Exit codes follow the source's widely scripted convention, so existing scripts keep working.
  • Silent add_to_* actions trade options for speed; the options dialog is one entry away.

Integrity

Status: Draft · Prefix: INT

Purpose

Checking that an archive decodes cleanly, and computing checksums of plain files.

Requirements

ID Requirement Verified by
INT-1 Test decodes every selected item (default all) without writing to disk and checks each stored checksum (CRC-32 in zip, 7z and gzip; block CRCs in bzip2; the stream check in xz). good archive → ok, nothing written
INT-2 Result: ok, or a list of {item_path, error}, with totals of files, folders, bytes tested and errors. 2 corrupt items → list of 2
INT-3 Error kinds shared by test, extract and codecs: crc_failed, data_error, unexpected_end, unsupported_method, wrong_password, headers_error, missing_volume, unavailable_data, data_after_end. data_after_end (bytes after a complete archive) is a warning; the result stays ok. truncated → unexpected_end; 100 bytes appended → ok + warning
INT-4 Encrypted archives need the password to test (ENC-5); a header-encrypted 7z cannot be tested without it. test without password → prompt
INT-5 Checksums of plain files and folders: crc32, crc64, xxh64, md5, sha1, sha256 (default), sha384, sha512, blake2sp. Output lists each file's value; for several files also one value over all data and one over data and names. Copy gives one value path line per file. sha256 of an empty file → e3b0c442…b855
INT-6 Testing a 1 GiB 7z (lzma2, level 5) takes at most 1.2 × the time to extract it to a fast local disk. manual: time test vs extract

Out of scope

  • Repairing damaged archives; recovery records; digital signatures.

Decisions

  • One error enum (INT-3) for test, extract and codecs keeps messages and exit codes (SHL-9) consistent.

Jobs

Status: Draft · Prefix: JOB

Purpose

Every create, update, extract, test or checksum run is a job the user can watch, pause and stop.

Requirements

ID Requirement Verified by
JOB-1 A job runs off the interface thread; windows stay responsive and several jobs can run at once, each with its own progress view. start 2 extractions → both progress, UI responds
JOB-2 Progress shows percent, elapsed, estimated remaining, speed (bytes/s), bytes and files processed of total, current item path, compression ratio (create), and error count; it updates at least every 500 ms. manual: watch a 2 GiB create job
JOB-3 Pause holds the job; resume continues where it stopped; background lowers its CPU priority. pause 10 s, resume → same result as unpaused
JOB-4 Cancel stops the job within 1 s and cleans up per CRT-10 and XTR-8; files finished before cancel stay. Quitting the app with running jobs asks first, listing them, then cancels them. cancel at 40% of 10 files → finished files remain, no partial
JOB-5 Outcome: succeeded, completed_with_warnings, completed_with_errors, failed, cancelled, shown with counts. A succeeded view closes by itself unless keep_job_view_open (default off); others stay until dismissed. 1 skipped input → completed_with_warnings, view stays
JOB-6 Messages list every warning and error as {severity, item_path, kind, detail} (INT-3); the list copies as text. copy → one line per message
JOB-7 On an item error the user picks skip_item or stop, except where this spec skips automatically. unreadable source → skip/stop offered
JOB-8 Temporary files go to temp_folder (default: the target's volume, so the final rename is atomic) and are removed at job end whatever the outcome. after cancel → no temp files
JOB-9 Packing 1 GiB of mixed files to 7z (level 5, all threads) keeps all logical CPUs busy for most of the run and stays within the memory estimate (FMT-9) + 10%. manual: monitor CPU and memory
JOB-10 Extracting a 4 GiB zip of 50,000 files runs at ≥ 70% of the speed of the OS copying the same unpacked tree on the same disk. manual: time both

Out of scope

  • Job queues that survive restarts; scheduling.

Decisions

  • Pause and cancel are checked between data blocks, which bounds their latency (JOB-4).

Names

Status: Draft · Prefix: NAM

Purpose

File names in any script survive packing and unpacking on every OS.

Requirements

ID Requirement Verified by
NAM-1 Names are Unicode internally. 7z stores UTF-16; tar (pax) and zip store UTF-8, and zip sets its UTF-8 flag on each item with a non-ASCII name. Stored paths use /; a \ from a Windows tool counts as a separator (SAFE-1). zip résumé.txt → UTF-8 flag set; zip a\b.txt → folder a, file b.txt
NAM-2 Zip items without the UTF-8 flag decode with legacy_code_page (default: the OS's legacy console code page on Windows, CP437 elsewhere); the browser lets the user pick another and relists at once. old Cyrillic zip garbled → pick CP866 → names correct
NAM-3 Bytes invalid in a name's encoding show as U+FFFD, are written to disk as _, and the rename is listed; the data still extracts. invalid byte in tar name → a_b.txt + rename listed
NAM-4 Characters the destination OS forbids (Windows: <>:"|?* and control characters) become _, and trailing dots and spaces are removed; each change is listed. a:b.txt on Windows → a_b.txt
NAM-5 Names reserved by the destination OS (Windows: CON, PRN, AUX, NUL, COM1–COM9, LPT1–LPT9, any extension) get a _ prefix. NUL.txt on Windows → _NUL.txt
NAM-6 Names differing only by case, or only by Unicode normalisation (NFC vs NFD), collide on a destination that treats them as equal; the second follows overwrite (XTR-5). Read.me + READ.ME on a case-insensitive disk → prompt on the second
NAM-7 A component longer than the destination allows (typically 255 bytes), or a path longer than the OS limit after any long-path support, fails that item with name_too_long; the job continues. 300-byte name → name_too_long, others extracted

Out of scope

  • Transliterating names.

Decisions

  • Prefixing reserved names keeps them readable and easy to fix by hand.

Extraction safety

Status: Draft · Prefix: SAFE

Purpose

An archive from anywhere can never write outside the chosen folder or plant something dangerous.

Requirements

ID Requirement Verified by
SAFE-1 Every stored path is normalised before use: \ and / are both separators, empty and . components are dropped, and .. cancels the previous component. a/./b//c → a/b/c; a/x/../b → a/b
SAFE-2 An item whose normalised path climbs above the destination (a .. with nothing left to cancel) is not written; it is reported unsafe_path and the job ends completed_with_errors. The check also covers codec paths (FMT-5), path_mode: none and renamed names. item ../../etc/x → nothing outside, unsafe_path
SAFE-3 Absolute stored paths (leading separator, drive letter C:, UNC \\host\share, device prefix \\?\) lose their root and land under the destination, with one warning per job; only path_mode: absolute with its confirmation (XTR-4) keeps the root. item /etc/passwd → <dest>/etc/passwd
SAFE-4 Symbolic links are created when extract_links is on (default on where the user may create links); where the OS forbids it, the link becomes a small text file holding its target, with a warning. Windows without link rights → text file + warning
SAFE-5 A link whose target is absolute, or resolves outside the destination from the link's folder, is not created (unsafe_link) unless allow_unsafe_links (default off) is on for this job. link x → /etc → skipped, unsafe_link
SAFE-6 Links are created only after every regular file of the job is written, so no file of the same archive is written through a link it created. link d → /tmp, then file d/x → d/x is a real file in <dest>
SAFE-7 If a component of a file's target path inside the destination is an existing link pointing outside it, the file is not written (unsafe_path). existing <dest>/d → /tmp, item d/x → refused
SAFE-8 Setuid, setgid and sticky bits are never restored; file owners are never changed. item mode 4755 → extracted 0755
SAFE-9 Hard-link items are created only when their target is an item this job already wrote inside the destination; otherwise unsafe_link. hard link to /etc/shadow → skipped
SAFE-10 Device files, FIFOs and sockets are never created; each is listed as skipped. tar with a character device → skipped
SAFE-11 When an item decodes to more than its declared size, decoding stops at that size and the item is data_error; total output never exceeds the sum of declared sizes. entry declaring 1 KiB, yielding 1 GiB → stops, error
SAFE-12 Extraction never runs or opens what it writes; nested archives stay files. Opening an executable, script or shortcut item from the browser (BRW-6) asks for confirmation first. open setup.exe from archive → confirmation

Out of scope

  • Malware scanning; antivirus integration.

Decisions

  • Unsafe items are skipped, not renamed into the destination: a renamed traversal payload would still surprise.
  • Links last (SAFE-6) plus the existing-link check (SAFE-7) close the link-then-write escape.
  • Without link rights, a text file with the target beats silently dropping the item.

From Specfork/archiver at 048b3a6, licence CC-BY-4.0.