Skip to main content

stuffr_cli/compat/zip/
flavour.rs

1//! The two builds the zip family can be, and one function per place their
2//! behaviour differs.
3//!
4//! The port follows Info-ZIP Zip 3.0 (July 5th 2008). Apple's build is
5//! `zip-33`'s `zip/zip30/` (its patch against upstream is `apple.diff`);
6//! Ubuntu's is `3.0-13ubuntu0.2`, which differs from upstream by its build
7//! (`BZIP2_SUPPORT` and `UNICODE_SUPPORT` on, `UIDGID_NOT_16BIT`) and a
8//! thirteen-patch series. Each difference the code meets is a function
9//! here, named for the behaviour, so a call site reads as the C's `#ifdef`
10//! did.
11//!
12//! **Measured, and overriding the spec (the record, `zip-measurements.md`):**
13//! Apple writes no 16-bit `Ux` field although its `-v` says `UIDGID_16BIT`
14//! (`unix.c` writes it only for a 16-bit `st_uid`): both builds write the
15//! same extra fields ([`writes_ux_16bit`] is false for both).
16//!
17//! **The build (Ubuntu's DEFINES):** `BZIP2_SUPPORT` ([`bzip2_support`]:
18//! `cli.rs`'s `-Z`, `zipup.rs`'s `bzfilecompress`, `-v`'s lines,
19//! [`last_known_method`]); `UNICODE_SUPPORT` ([`unicode_support`]: the
20//! option table, `-v`, [`shows_newline_escaped`], and every site in
21//! `unicode.rs`, `readzip.rs` (the central and local names), `zipfile.rs`
22//! (putlocal/putcentral), `scan.rs` (`-su`/`-sU`, `-UN escape`, matching by
23//! the Unicode name), `fix.rs` (`-FF`), `zipup.rs` (a new entry's UTF-8
24//! name)); `UIDGID_NOT_16BIT` ([`writes_ux_16bit`], no effect: neither build
25//! writes the field).
26//!
27//! **The patch series** (`debian/patches/series`, in order):
28//!
29//! | Patch | Effect | Hook |
30//! |---|---|---|
31//! | 01 typo "transferring" | `man/zip.1`, `zip.txt` only | none: no binary string changes |
32//! | 02 typo "privileges" | man page only | none |
33//! | 03 man pages in section 1 | man page only | none |
34//! | 04 no unwanted CFLAGS | build flags (`-O3` dropped) | none: no behaviour |
35//! | 05 typo "preceding" | man page only | none |
36//! | 06 stack markings in i386 assembly | `crc_i386.S`, `match.S` (x86 only) | none: no behaviour |
37//! | 07 `fclose(in_file)`, not `fclose(x)` | zipnote `-w` works (Apple's fails: closes the temp's stream) | none: both flavours take 07 (deviation 26, ruling Z4) |
38//! | 08 `printf("%s", text)` | help/licence texts have no `%`: same output | none |
39//! | 09 `LDFLAGS` | build only | none |
40//! | 10 no build date | `-v`'s `Compiled with gcc 13.3.0 for Unix (Linux ELF).` has no date | [`version_post`] (the recorded text) |
41//! | 11 typo "ambiguities" | man page only | none |
42//! | 12 `mbstowcs(..., wsize + 1)` | without it glibc's fortify aborts on any non-ASCII name | none: the defined, patched behaviour is the only one (`unicode::local_to_wide`; deviation 28, `ub_rulings`) |
43//! | 13 `-TT` buffer `+ 4` | without it one byte is written past the command | none: stuffr builds the command as a string (`-TT` in `zip.c:check_zipfile`'s port; deviation 28, `ub_rulings`) |
44//!
45//! Every Ubuntu row is compared with the reference by
46//! `compat_zip_ubuntu::ubuntu_build_rows` (Docker) and the shared table.
47//!
48//! Hooks used by the front end: [`bzip2_support`], [`unicode_support`],
49//! [`help2_brace_line`], [`tt_replaces_braces`], [`test_through_shell`],
50//! [`shows_newline_escaped`], [`last_known_method`],
51//! [`stale_errno_after_warning`], [`fnmax`], [`origin`], [`version_pre`],
52//! [`version_post`].
53
54/// Which reference the family behaves as.
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum Flavour {
57    /// Apple's `/usr/bin/zip` (Zip 3.0 with Apple's changes): the default
58    /// on macOS and the BSDs.
59    Apple,
60    /// Ubuntu 24.04's apt `zip` `3.0-13ubuntu0.2`: the default elsewhere.
61    Ubuntu,
62}
63
64/// `-DBZIP2_SUPPORT`, Ubuntu only: `-Z bzip2` (`-Z b`) is accepted and
65/// means method 12, the `-Z` complaint lists `bzip2`, and `-v` has the
66/// `BZIP2_SUPPORT` lines. Apple's build has none: `-Z bzip2` is exit 19
67/// ("Compression method bzip2 not enabled").
68pub(crate) fn bzip2_support(f: Flavour) -> bool {
69    f == Flavour::Ubuntu
70}
71
72/// `-DUNICODE_SUPPORT`, Ubuntu only: the option table gains `-su`, `-sU`
73/// and `-UN` (`unicode`), `-v` has the `UNICODE_SUPPORT` line, names get
74/// the UTF-8 flag or the Unicode path field (`unicode.rs`), and a newline
75/// in a name is shown as `^J`. Apple's table has none of the three
76/// options: `-su` is `-s u` (a bad size), `--show-unicode` is not
77/// supported.
78pub(crate) fn unicode_support(f: Flavour) -> bool {
79    f == Flavour::Ubuntu
80}
81
82/// Whether the 16-bit `Ux` extra field is written: neither build writes it
83/// (ruling Z3, measured: the field appears only for a 16-bit `st_uid`). For
84/// Task 6, which writes the extra fields.
85pub(crate) fn writes_ux_16bit(_f: Flavour) -> bool {
86    false
87}
88
89/// apple.diff (zip.c:925): Apple comments out the `In cmd, {} replaced by
90/// temp archive path, else temp appended.` line of `-h2`; Ubuntu keeps it
91/// (and its `-TT` replaces the first `{}`, see [`tt_replaces_braces`]).
92pub(crate) fn help2_brace_line(f: Flavour) -> bool {
93    f == Flavour::Ubuntu
94}
95
96/// `-TT cmd` under Ubuntu is run through `system("cmd 'temp'")` with the
97/// first `{}` replaced by the quoted temp path; under Apple (apple.diff,
98/// zip.c:1425) it is `posix_spawnp` of `cmd` as ONE program name with argv
99/// `[cmd, temp]`, and a `{}` is refused (`ZE_PARMS`, exit 16).
100pub(crate) fn tt_replaces_braces(f: Flavour) -> bool {
101    f == Flavour::Ubuntu
102}
103
104/// Ubuntu shows a newline in an entry name as `^J` in its messages (its
105/// `UNICODE_SUPPORT` display path); Apple shows it raw.
106pub(crate) fn shows_newline_escaped(f: Flavour) -> bool {
107    f == Flavour::Ubuntu
108}
109
110/// zip.h:LAST_KNOWN_COMPMETHOD, which `-v`'s `zipoddities` compares an
111/// entry's method with: BZIP2 (12) with Ubuntu's `BZIP2_SUPPORT`, DEFLATE
112/// (8) for Apple.
113pub(crate) fn last_known_method(f: Flavour) -> u16 {
114    if bzip2_support(f) { 12 } else { 8 }
115}
116
117/// `-T` (zip.c:check_zipfile): Apple runs `unzip` and a `-TT` command with
118/// `posix_spawnp` (no shell; `{}` refused), Ubuntu through `system()`
119/// (`/bin/sh -c`, the temp path quoted, `{}` replaced). Same as
120/// [`tt_replaces_braces`].
121pub(crate) fn test_through_shell(f: Flavour) -> bool {
122    tt_replaces_braces(f)
123}
124
125/// zip.c:main, `-MM` and an input that could not be opened: Ubuntu's
126/// `zip I/O error:` line says `Invalid argument`, the `errno` glibc's
127/// stdio leaves after the warnings before it; Apple's says why the open
128/// failed (measured, the record's code table, exit 18).
129pub(crate) fn stale_errno_after_warning(f: Flavour) -> bool {
130    f == Flavour::Ubuntu
131}
132
133/// tailor.h:FNMAX (`PATH_MAX`): macOS's 1024, glibc's 4096. The split
134/// prompts read lines of this size (`fgets(buf, FNMAX, stdin)` for `-sp`,
135/// `FNMAX + 4010` when asking where a part to read is), so a longer answer
136/// is read in pieces, each its own answer.
137pub(crate) fn fnmax(f: Flavour) -> usize {
138    match f {
139        Flavour::Apple => 1024,
140        Flavour::Ubuntu => 4096,
141    }
142}
143
144/// Where the reference came from, for the banner: stuffr's banner line is
145/// the reference's, by stuffr, marked as altered (Info-ZIP licence clause
146/// 3), and says whose build it alters.
147pub(crate) fn origin(f: Flavour) -> &'static str {
148    match f {
149        // apple: revision.h (`by Info-ZIP, with modifications by Apple Inc.`)
150        Flavour::Apple => super::messages::APPLE_ORIGIN,
151        // ubuntu: 02-typo-it-is-privileges-not-priviliges is man-page only;
152        // the build is Debian's package of upstream's banner
153        Flavour::Ubuntu => super::messages::UBUNTU_ORIGIN,
154    }
155}
156
157/// The preamble of `-v` for `name`, flavour `f`: the recorded text before
158/// the banner line.
159pub(crate) fn version_pre(f: Flavour, name: &str) -> &'static str {
160    use super::messages as m;
161    match (name, f) {
162        ("zip", Flavour::Apple) => m::ZIP_V_APPLE_PRE,
163        ("zip", Flavour::Ubuntu) => m::ZIP_V_UBUNTU_PRE,
164        ("zipcloak", Flavour::Apple) => m::ZIPCLOAK_V_APPLE_PRE,
165        ("zipcloak", Flavour::Ubuntu) => m::ZIPCLOAK_V_UBUNTU_PRE,
166        ("zipnote", Flavour::Apple) => m::ZIPNOTE_V_APPLE_PRE,
167        ("zipnote", Flavour::Ubuntu) => m::ZIPNOTE_V_UBUNTU_PRE,
168        (_, Flavour::Apple) => m::ZIPSPLIT_V_APPLE_PRE,
169        (_, Flavour::Ubuntu) => m::ZIPSPLIT_V_UBUNTU_PRE,
170    }
171}
172
173/// What follows the banner line of `-v` for `name`, flavour `f`.
174pub(crate) fn version_post(f: Flavour, name: &str) -> &'static str {
175    use super::messages as m;
176    match (name, f) {
177        ("zip", Flavour::Apple) => m::ZIP_V_APPLE_POST,
178        ("zip", Flavour::Ubuntu) => m::ZIP_V_UBUNTU_POST,
179        ("zipcloak", Flavour::Apple) => m::ZIPCLOAK_V_APPLE_POST,
180        ("zipcloak", Flavour::Ubuntu) => m::ZIPCLOAK_V_UBUNTU_POST,
181        ("zipnote", Flavour::Apple) => m::ZIPNOTE_V_APPLE_POST,
182        ("zipnote", Flavour::Ubuntu) => m::ZIPNOTE_V_UBUNTU_POST,
183        (_, Flavour::Apple) => m::ZIPSPLIT_V_APPLE_POST,
184        (_, Flavour::Ubuntu) => m::ZIPSPLIT_V_UBUNTU_POST,
185    }
186}