use std::ffi::OsString;
use std::io::{self, BufRead, Read};
use super::flavour::{self, Flavour};
use super::messages::{self as m, printf};
use super::scan;
use super::{Exit, Zr};
pub(crate) const ZE_OK: i32 = 0;
pub(crate) const ZE_ABORT: i32 = 9;
pub(crate) const ZE_PARMS: i32 = 16;
pub(crate) const ZE_OPEN: i32 = 18;
pub(crate) const ZE_COMPERR: i32 = 19;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ValueType {
No,
Required,
#[allow(dead_code)]
Optional,
List,
#[allow(dead_code)]
OneChar,
#[allow(dead_code)]
Number,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct OptionSpec {
pub(crate) short: &'static str,
pub(crate) long: &'static str,
pub(crate) value: ValueType,
pub(crate) negatable: bool,
pub(crate) id: u32,
pub(crate) name: &'static str,
}
pub(crate) mod o {
pub(crate) const DB: u32 = 0x105;
pub(crate) const DC: u32 = 0x106;
pub(crate) const DD: u32 = 0x107;
pub(crate) const DES: u32 = 0x108;
pub(crate) const DF_CAP: u32 = 0x110;
pub(crate) const DG: u32 = 0x111;
pub(crate) const DS: u32 = 0x112;
pub(crate) const DU: u32 = 0x113;
pub(crate) const DV: u32 = 0x114;
pub(crate) const FF: u32 = 0x115;
pub(crate) const FI: u32 = 0x116;
pub(crate) const FS: u32 = 0x117;
pub(crate) const H2: u32 = 0x118;
pub(crate) const LA: u32 = 0x121;
pub(crate) const LF: u32 = 0x122;
pub(crate) const LI: u32 = 0x123;
pub(crate) const LL: u32 = 0x124;
pub(crate) const MM: u32 = 0x125;
pub(crate) const MM_CAP: u32 = 0x126;
pub(crate) const NW: u32 = 0x127;
pub(crate) const RE: u32 = 0x128;
pub(crate) const SB: u32 = 0x129;
pub(crate) const SC: u32 = 0x130;
pub(crate) const SD: u32 = 0x131;
pub(crate) const SF: u32 = 0x132;
pub(crate) const SO: u32 = 0x133;
pub(crate) const SP: u32 = 0x134;
pub(crate) const SU: u32 = 0x135;
pub(crate) const SU_CAP: u32 = 0x136;
pub(crate) const SV: u32 = 0x137;
pub(crate) const TT: u32 = 0x138;
pub(crate) const TT_CAP: u32 = 0x139;
pub(crate) const UN: u32 = 0x140;
pub(crate) const VE: u32 = 0x141;
pub(crate) const WS: u32 = 0x143;
pub(crate) const Z64: u32 = 0x145;
pub(crate) const NON_OPTION_ARG: u32 = 0xFFFF;
}
const fn spec(
short: &'static str,
long: &'static str,
value: ValueType,
negatable: bool,
id: u32,
name: &'static str,
) -> OptionSpec {
OptionSpec {
short,
long,
value,
negatable,
id,
name,
}
}
use ValueType::{List as VList, No as VNo, Required as VReq};
const fn ch(c: char) -> u32 {
c as u32
}
pub(crate) fn table(f: Flavour) -> Vec<OptionSpec> {
let mut t = vec![
spec("0", "store", VNo, false, ch('0'), "store"),
spec("1", "compress-1", VNo, false, ch('1'), "compress 1"),
spec("2", "compress-2", VNo, false, ch('2'), "compress 2"),
spec("3", "compress-3", VNo, false, ch('3'), "compress 3"),
spec("4", "compress-4", VNo, false, ch('4'), "compress 4"),
spec("5", "compress-5", VNo, false, ch('5'), "compress 5"),
spec("6", "compress-6", VNo, false, ch('6'), "compress 6"),
spec("7", "compress-7", VNo, false, ch('7'), "compress 7"),
spec("8", "compress-8", VNo, false, ch('8'), "compress 8"),
spec("9", "compress-9", VNo, false, ch('9'), "compress 9"),
spec(
"A",
"adjust-sfx",
VNo,
false,
ch('A'),
"adjust self extractor offsets",
),
spec(
"b",
"temp-path",
VReq,
false,
ch('b'),
"dir to use for temp archive",
),
spec(
"c",
"entry-comments",
VNo,
false,
ch('c'),
"add comments for each entry",
),
spec(
"d",
"delete",
VNo,
false,
ch('d'),
"delete entries from archive",
),
spec(
"db",
"display-bytes",
VNo,
true,
o::DB,
"display running bytes",
),
spec(
"dc",
"display-counts",
VNo,
true,
o::DC,
"display running file count",
),
spec(
"dd",
"display-dots",
VNo,
true,
o::DD,
"display dots as process each file",
),
spec(
"dg",
"display-globaldots",
VNo,
true,
o::DG,
"display dots for archive instead of files",
),
spec(
"ds",
"dot-size",
VReq,
false,
o::DS,
"set progress dot size - default 10M bytes",
),
spec(
"du",
"display-usize",
VNo,
true,
o::DU,
"display uncompressed size in bytes",
),
spec(
"dv",
"display-volume",
VNo,
true,
o::DV,
"display volume (disk) number",
),
spec(
"D",
"no-dir-entries",
VNo,
false,
ch('D'),
"no entries for dirs themselves (-x */)",
),
spec(
"DF",
"difference-archive",
VNo,
false,
o::DF_CAP,
"create diff archive with changed/new files",
),
spec(
"e",
"encrypt",
VNo,
false,
ch('e'),
"encrypt entries, ask for password",
),
spec(
"F",
"fix",
VNo,
false,
ch('F'),
"fix mostly intact archive (try first)",
),
spec(
"FF",
"fixfix",
VNo,
false,
o::FF,
"try harder to fix archive (not as reliable)",
),
spec(
"FI",
"fifo",
VNo,
true,
o::FI,
"read Unix FIFO (zip will wait on open pipe)",
),
spec(
"FS",
"filesync",
VNo,
false,
o::FS,
"add/delete entries to make archive match OS",
),
spec(
"f",
"freshen",
VNo,
false,
ch('f'),
"freshen existing archive entries",
),
spec(
"fd",
"force-descriptors",
VNo,
false,
o::DES,
"force data descriptors as if streaming",
),
spec(
"fz",
"force-zip64",
VNo,
true,
o::Z64,
"force use of Zip64 format, negate prevents",
),
spec(
"g",
"grow",
VNo,
false,
ch('g'),
"grow existing archive instead of replace",
),
spec("h", "help", VNo, false, ch('h'), "help"),
spec("H", "", VNo, false, ch('h'), "help"),
spec("?", "", VNo, false, ch('h'), "help"),
spec("h2", "more-help", VNo, false, o::H2, "extended help"),
spec(
"i",
"include",
VList,
false,
ch('i'),
"include only files matching patterns",
),
spec(
"j",
"junk-paths",
VNo,
false,
ch('j'),
"strip paths and just store file names",
),
spec(
"J",
"junk-sfx",
VNo,
false,
ch('J'),
"strip self extractor from archive",
),
spec(
"k",
"DOS-names",
VNo,
false,
ch('k'),
"force use of 8.3 DOS names",
),
spec(
"l",
"to-crlf",
VNo,
false,
ch('l'),
"convert text file line ends - LF->CRLF",
),
spec(
"ll",
"from-crlf",
VNo,
false,
o::LL,
"convert text file line ends - CRLF->LF",
),
spec(
"lf",
"logfile-path",
VReq,
false,
o::LF,
"log to log file at path (default overwrite)",
),
spec(
"la",
"log-append",
VNo,
true,
o::LA,
"append to existing log file",
),
spec(
"li",
"log-info",
VNo,
true,
o::LI,
"include informational messages in log",
),
spec("L", "license", VNo, false, ch('L'), "display license"),
spec(
"m",
"move",
VNo,
false,
ch('m'),
"add files to archive then delete files",
),
spec("mm", "", VNo, false, o::MM, "not used"),
spec(
"MM",
"must-match",
VNo,
false,
o::MM_CAP,
"error if in file not matched/not readable",
),
spec(
"n",
"suffixes",
VReq,
false,
ch('n'),
"suffixes to not compress: .gz:.zip",
),
spec(
"nw",
"no-wild",
VNo,
false,
o::NW,
"no wildcards during add or update",
),
spec(
"o",
"latest-time",
VNo,
false,
ch('o'),
"use latest entry time as archive time",
),
spec(
"O",
"output-file",
VReq,
false,
ch('O'),
"set out zipfile different than in zipfile",
),
spec("p", "paths", VNo, false, ch('p'), "store paths"),
spec(
"P",
"password",
VReq,
false,
ch('P'),
"encrypt entries, option value is password",
),
spec("q", "quiet", VNo, false, ch('q'), "quiet"),
spec(
"r",
"recurse-paths",
VNo,
false,
ch('r'),
"recurse down listed paths",
),
spec(
"R",
"recurse-patterns",
VNo,
false,
ch('R'),
"recurse current dir and match patterns",
),
spec(
"RE",
"regex",
VNo,
false,
o::RE,
"allow [list] matching (regex)",
),
spec(
"s",
"split-size",
VReq,
false,
ch('s'),
"do splits, set split size (-s=0 no splits)",
),
spec(
"sp",
"split-pause",
VNo,
false,
o::SP,
"pause while splitting to select destination",
),
spec(
"sv",
"split-verbose",
VNo,
false,
o::SV,
"be verbose about creating splits",
),
spec(
"sb",
"split-bell",
VNo,
false,
o::SB,
"when pause for next split ring bell",
),
spec("sc", "show-command", VNo, false, o::SC, "show command line"),
spec("sd", "show-debug", VNo, false, o::SD, "show debug"),
spec(
"sf",
"show-files",
VNo,
true,
o::SF,
"show files to operate on and exit",
),
spec("so", "show-options", VNo, false, o::SO, "show options"),
spec(
"t",
"from-date",
VReq,
false,
ch('t'),
"exclude before date",
),
spec(
"tt",
"before-date",
VReq,
false,
o::TT,
"include before date",
),
spec(
"T",
"test",
VNo,
false,
ch('T'),
"test updates before replacing archive",
),
spec(
"TT",
"unzip-command",
VReq,
false,
o::TT_CAP,
"unzip command to use, name is added to end",
),
spec(
"u",
"update",
VNo,
false,
ch('u'),
"update existing entries and add new",
),
spec(
"U",
"copy-entries",
VNo,
false,
ch('U'),
"select from archive instead of file system",
),
spec(
"v",
"verbose",
VNo,
false,
ch('v'),
"display additional information",
),
spec(
"",
"version",
VNo,
false,
o::VE,
"(if no other args) show version information",
),
spec(
"ws",
"wild-stop-dirs",
VNo,
false,
o::WS,
"* stops at /, ** includes any /",
),
spec(
"x",
"exclude",
VList,
false,
ch('x'),
"exclude files matching patterns",
),
spec(
"X",
"strip-extra",
VNo,
true,
ch('X'),
"-X- keep all ef, -X strip but critical ef",
),
spec("y", "symlinks", VNo, false, ch('y'), "store symbolic links"),
spec(
"z",
"archive-comment",
VNo,
false,
ch('z'),
"ask for archive comment",
),
spec(
"Z",
"compression-method",
VReq,
false,
ch('Z'),
"compression method",
),
spec(
"@",
"names-stdin",
VNo,
false,
ch('@'),
"get file names from stdin, one per line",
),
];
if flavour::unicode_support(f) {
let at = t
.iter()
.position(|s| s.short == "so")
.map_or(t.len(), |i| i + 1);
t.insert(
at,
spec(
"su",
"show-unicode",
VNo,
true,
o::SU,
"as -sf but also show escaped Unicode",
),
);
t.insert(
at + 1,
spec(
"sU",
"show-just-unicode",
VNo,
true,
o::SU_CAP,
"as -sf but only show escaped Unicode",
),
);
let at = t
.iter()
.position(|s| s.short == "U")
.map_or(t.len(), |i| i + 1);
t.insert(
at,
spec(
"UN",
"unicode",
VReq,
false,
o::UN,
"UN=quit, warn, ignore, no, escape",
),
);
}
t
}
pub(crate) fn cloak_table() -> Vec<OptionSpec> {
vec![
spec("b", "temp-path", VReq, false, ch('b'), "path for temp file"),
spec("d", "decrypt", VNo, false, ch('d'), "decrypt"),
spec("h", "help", VNo, false, ch('h'), "help"),
spec("L", "license", VNo, false, ch('L'), "license"),
spec("l", "", VNo, false, ch('L'), "license"),
spec(
"O",
"output-file",
VReq,
false,
ch('O'),
"output to new archive",
),
spec("v", "version", VNo, false, ch('v'), "version"),
]
}
const SKIP_VALUE_ARG: i64 = -1;
const THIS_ARG_DONE: i64 = -2;
const START_VALUE_LIST: i64 = -3;
const IN_VALUE_LIST: i64 = -4;
const NON_OPTION_ARG: i64 = -5;
const STOP_VALUE_LIST: i64 = -6;
const READ_REST_ARGS_VERBATIM: i64 = -7;
const NO_OPTION_MATCH: i64 = -1;
#[derive(Debug, Clone, Copy)]
pub(crate) struct GetOpt {
pub(crate) argnum: i64,
pub(crate) optchar: i64,
pub(crate) first_nonopt_arg: i64,
pub(crate) option_num: i64,
}
impl GetOpt {
pub(crate) fn new() -> GetOpt {
GetOpt {
argnum: 0,
optchar: 0,
first_nonopt_arg: 0,
option_num: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Got {
pub(crate) id: u32,
pub(crate) value: Option<Vec<u8>>,
pub(crate) negated: bool,
}
fn at(args: &[Vec<u8>], i: i64) -> Option<&Vec<u8>> {
usize::try_from(i).ok().and_then(|i| args.get(i))
}
fn byte(s: &[u8], i: i64) -> u8 {
usize::try_from(i)
.ok()
.and_then(|i| s.get(i))
.copied()
.unwrap_or(0)
}
fn set(args: &mut [Vec<u8>], j: i64, v: Vec<u8>) {
if let Some(slot) = usize::try_from(j).ok().and_then(|j| args.get_mut(j)) {
*slot = v;
}
}
fn optionerr(err: &str, spec: &OptionSpec, islong: bool) -> Vec<u8> {
let mut optname = Vec::new();
let nm = if islong { spec.long } else { spec.short };
if !spec.name.is_empty() {
printf(
&mut optname,
m::OPTION_NAME_DESC,
&[nm.as_bytes(), spec.name.as_bytes()],
);
} else {
printf(&mut optname, m::OPTION_NAME, &[nm.as_bytes()]);
}
let mut buf = Vec::new();
printf(&mut buf, err, &[&optname]);
buf
}
fn insert_arg(args: &mut Vec<Vec<u8>>, arg: &[u8], at_arg: i64) {
let at = usize::try_from(at_arg).unwrap_or(0).min(args.len());
args.insert(at, arg.to_vec());
}
fn get_shortopt(
table: &[OptionSpec],
args: &[Vec<u8>],
argnum: i64,
optchar: &mut i64,
negated: &mut bool,
value: &mut Option<Vec<u8>>,
option_num: &mut i64,
) -> Result<u32, Vec<u8>> {
let arg: &[u8] = at(args, argnum).map_or(&[], |a| a.as_slice());
*optchar += 1;
let shortopt = *optchar;
*value = None;
if byte(arg, shortopt) == 0 {
*optchar = 0;
*option_num = NO_OPTION_MATCH;
return Ok(0);
}
let mut matched: Option<usize> = None;
for (op, o) in table.iter().enumerate() {
let s = o.short.as_bytes();
if !s.is_empty() && s[0] == byte(arg, shortopt) {
if s.len() == 1 {
matched = Some(op);
} else if s[1] == byte(arg, shortopt + 1) {
matched = Some(op);
*optchar += 1;
break;
}
}
}
let Some(mi) = matched else {
let mut e = Vec::new();
printf(&mut e, m::SH_OP_NOT_SUP_ERR, &[&[byte(arg, shortopt)]]);
return Err(e);
};
let mo = &table[mi];
if byte(arg, *optchar + 1) == b'-' {
if !mo.negatable {
if mo.value == ValueType::No {
return Err(optionerr(m::OP_NOT_NEG_ERR, mo, false));
}
} else {
*negated = true;
*optchar += 1;
}
}
let after = *optchar + 1;
match mo.value {
ValueType::OneChar => {
if byte(arg, after) != 0 {
*value = Some(vec![byte(arg, after)]);
*optchar += 1;
} else {
return Err(optionerr(m::OCO_REQ_VAL_ERR, mo, false));
}
}
ValueType::Number => {
let mut start = after;
if matches!(byte(arg, start), b'+' | b'-') {
start += 1;
}
let mut s = start;
while byte(arg, s).is_ascii_digit() {
s += 1;
}
if s == start {
return Err(optionerr(m::NUM_REQ_VAL_ERR, mo, false));
}
let from = usize::try_from(after).unwrap_or(0);
let to = usize::try_from(s).unwrap_or(0);
*value = Some(arg[from..to].to_vec());
*optchar += s - after;
}
ValueType::Optional => {
if byte(arg, after) != 0 {
let mut clen = 1;
if byte(arg, *optchar + clen) == b'=' {
clen += 1;
}
if byte(arg, *optchar + clen) != 0 {
let from = usize::try_from(*optchar + clen).unwrap_or(0);
*value = Some(arg[from..].to_vec());
}
*optchar = THIS_ARG_DONE;
} else if let Some(next) = at(args, argnum + 1)
&& next.first() != Some(&b'-')
{
*value = Some(next.clone());
*optchar = SKIP_VALUE_ARG;
}
}
ValueType::Required | ValueType::List => {
if byte(arg, after) != 0 {
let mut clen = 1;
if byte(arg, *optchar + clen) == b'=' {
clen += 1;
}
let from = usize::try_from(*optchar + clen).unwrap_or(0);
*value = Some(arg[from..].to_vec());
*optchar = THIS_ARG_DONE;
} else if let Some(next) = at(args, argnum + 1) {
*value = Some(next.clone());
*optchar = if mo.value == ValueType::List {
START_VALUE_LIST
} else {
SKIP_VALUE_ARG
};
} else {
return Err(optionerr(m::OP_REQ_VAL_ERR, mo, false));
}
}
ValueType::No => {}
}
*option_num = i64::try_from(mi).unwrap_or(NO_OPTION_MATCH);
Ok(mo.id)
}
fn get_longopt(
table: &[OptionSpec],
args: &[Vec<u8>],
argnum: i64,
optchar: &mut i64,
negated: &mut bool,
value: &mut Option<Vec<u8>>,
option_num: &mut i64,
) -> Result<u32, Vec<u8>> {
*value = None;
let Some(arg) = at(args, argnum) else {
*option_num = NO_OPTION_MATCH;
return Ok(0);
};
let body = &arg[2.min(arg.len())..];
let (mut longopt, valuestart) = match body.iter().position(|&b| b == b'=') {
Some(i) => (body[..i].to_vec(), Some(body[i + 1..].to_vec())),
None => (body.to_vec(), None),
};
*negated = longopt.last() == Some(&b'-');
if *negated {
longopt.pop();
}
let mut matched: Option<usize> = None;
for (op, o) in table.iter().enumerate() {
let l = o.long.as_bytes();
if l == longopt.as_slice() {
matched = Some(op);
break;
}
if l.starts_with(&longopt) {
if matched.is_some() {
let mut e = Vec::new();
printf(&mut e, m::LONG_OP_AMBIG_ERR, &[&longopt]);
return Err(e);
}
matched = Some(op);
}
}
let Some(mi) = matched else {
let mut e = Vec::new();
printf(&mut e, m::LONG_OP_NOT_SUP_ERR, &[&longopt]);
return Err(e);
};
let mo = &table[mi];
*optchar = THIS_ARG_DONE;
if *negated && !mo.negatable {
return Err(optionerr(m::OP_NOT_NEG_ERR, mo, true));
}
match mo.value {
ValueType::Optional => {
if let Some(v) = valuestart {
*value = Some(v);
}
}
ValueType::Required | ValueType::Number | ValueType::OneChar | ValueType::List => {
if let Some(v) = valuestart {
*value = Some(v);
} else if let Some(next) = at(args, argnum + 1) {
*value = Some(next.clone());
*optchar = if mo.value == ValueType::List {
START_VALUE_LIST
} else {
SKIP_VALUE_ARG
};
} else {
return Err(optionerr(m::OP_REQ_VAL_ERR, mo, true));
}
}
ValueType::No => {
if valuestart.is_some() {
return Err(optionerr(m::OP_NO_ALLOW_VAL_ERR, mo, true));
}
}
}
*option_num = i64::try_from(mi).unwrap_or(NO_OPTION_MATCH);
Ok(mo.id)
}
pub(crate) fn get_option(
table: &[OptionSpec],
args: &mut Vec<Vec<u8>>,
st: &mut GetOpt,
) -> Result<Got, Vec<u8>> {
let mut value: Option<Vec<u8>> = None;
let mut negated = false;
let mut argcnt = args.len() as i64;
if argcnt < 2 {
return Ok(Got {
id: 0,
value,
negated,
});
}
let mut first_nonoption_arg = st.first_nonopt_arg;
let mut argn = st.argnum;
let mut optc = st.optchar;
let mut option_num = st.option_num;
let mut read_rest_args_verbatim = optc == READ_REST_ARGS_VERBATIM;
if argn == -1 || argn == 0 {
option_num = NO_OPTION_MATCH;
optc = THIS_ARG_DONE;
first_nonoption_arg = -1;
}
let mut option_id: u32 = 0;
if option_num != NO_OPTION_MATCH
&& let Some(s) = usize::try_from(option_num).ok().and_then(|i| table.get(i))
{
option_id = s.id;
}
loop {
if read_rest_args_verbatim {
argn += 1;
if argn > argcnt || at(args, argn).is_none() {
option_id = 0;
break;
}
value = at(args, argn).cloned();
option_num = NO_OPTION_MATCH;
option_id = o::NON_OPTION_ARG;
break;
}
if matches!(
optc,
SKIP_VALUE_ARG | THIS_ARG_DONE | START_VALUE_LIST | IN_VALUE_LIST | STOP_VALUE_LIST
) && first_nonoption_arg > -1
&& at(args, first_nonoption_arg).is_some()
{
let v = i64::from(optc == SKIP_VALUE_ARG || optc == START_VALUE_LIST);
for _ in first_nonoption_arg..argn {
let moved = at(args, first_nonoption_arg).cloned().unwrap_or_default();
for j in first_nonoption_arg..argn + v {
let next = at(args, j + 1).cloned().unwrap_or_default();
set(args, j, next);
}
set(args, argn + v, moved);
}
first_nonoption_arg += 1 + v;
}
if optc == STOP_VALUE_LIST {
optc = THIS_ARG_DONE;
}
if optc == START_VALUE_LIST || optc == IN_VALUE_LIST {
if optc == START_VALUE_LIST {
argn += 1;
optc = IN_VALUE_LIST;
}
argn += 1;
if at(args, argn).is_none() && first_nonoption_arg > -1 {
insert_arg(args, b"@", first_nonoption_arg);
argcnt = args.len() as i64;
argn += 1;
first_nonoption_arg += 1;
}
match at(args, argn) {
Some(a) if a.as_slice() == b"@" => {
optc = STOP_VALUE_LIST;
continue;
}
Some(a) if a.first() != Some(&b'-') => {
value = Some(a.clone());
break;
}
_ => {
argn -= 1;
optc = THIS_ARG_DONE;
}
}
}
if optc == SKIP_VALUE_ARG {
argn += 2;
optc = 0;
} else if optc == THIS_ARG_DONE {
argn += 1;
optc = 0;
}
if argn > argcnt {
break;
}
if at(args, argn).is_none() {
if first_nonoption_arg > -1 && at(args, first_nonoption_arg).is_some() {
if optc == NON_OPTION_ARG {
first_nonoption_arg += 1;
}
std::mem::swap(&mut argn, &mut first_nonoption_arg);
}
if argn > argcnt || at(args, argn).is_none() {
option_id = 0;
break;
}
}
if first_nonoption_arg > -1 && at(args, first_nonoption_arg).is_none() {
if optc == NON_OPTION_ARG {
argn += 1;
}
if argn > argcnt || at(args, argn).is_none() {
option_id = 0;
break;
}
value = at(args, argn).cloned();
optc = NON_OPTION_ARG;
option_id = o::NON_OPTION_ARG;
break;
}
let arg = at(args, argn).cloned().unwrap_or_default();
if arg.first() == Some(&b'-') {
if arg.len() == 1 {
option_num = NO_OPTION_MATCH;
if first_nonoption_arg < 0 {
first_nonoption_arg = argn;
}
argn += 1;
} else if arg[1] == b'-' {
if arg.len() == 2 {
if first_nonoption_arg < 1 {
argn -= 1;
} else {
argn = first_nonoption_arg - 1;
}
read_rest_args_verbatim = true;
optc = READ_REST_ARGS_VERBATIM;
} else {
option_id = get_longopt(
table,
args,
argn,
&mut optc,
&mut negated,
&mut value,
&mut option_num,
)?;
break;
}
} else {
option_id = get_shortopt(
table,
args,
argn,
&mut optc,
&mut negated,
&mut value,
&mut option_num,
)?;
if optc == 0 {
optc = THIS_ARG_DONE;
} else {
break;
}
}
} else {
if first_nonoption_arg < 0 {
first_nonoption_arg = argn;
}
argn += 1;
}
}
st.first_nonopt_arg = first_nonoption_arg;
st.argnum = argn;
st.optchar = optc;
st.option_num = option_num;
Ok(Got {
id: option_id,
value,
negated,
})
}
fn is_space(b: u8) -> bool {
matches!(b, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
}
pub(crate) fn envargs(
args: Vec<Vec<u8>>,
env: &dyn Fn(&str) -> Option<OsString>,
first: &str,
second: &str,
) -> Vec<Vec<u8>> {
let get = |name: &str| -> Option<Vec<u8>> {
let v = env(name)?.as_encoded_bytes().to_vec();
let skip = v.iter().take_while(|&&b| is_space(b)).count();
Some(v[skip..].to_vec())
};
let mut envptr = get(first);
if envptr.as_ref().is_none_or(|v| v.is_empty()) {
envptr = get(second);
}
let Some(s) = envptr.filter(|v| !v.is_empty()) else {
return args;
};
let mut out = Vec::with_capacity(args.len() + 4);
let mut rest = args.into_iter();
out.extend(rest.next());
let at = |i: usize| s.get(i).copied().unwrap_or(0);
let mut p = 0usize;
loop {
if at(p) == b'"' {
p += 1;
let start = p;
let mut ch = at(p);
while ch != 0 && ch != b'"' {
if ch == b'\\' && at(p + 1) != 0 {
p += 1;
}
p += 1;
ch = at(p);
}
let mut word = s[start..p.min(s.len())].to_vec();
if ch != 0 {
p += 1;
}
let mut i = 0;
while i < word.len() {
if word[i] == b'\\' {
word.remove(i);
if i < word.len() {
i += 1;
}
} else {
i += 1;
}
}
out.push(word);
} else {
let start = p;
while at(p) != 0 && !is_space(at(p)) {
p += 1;
}
out.push(s[start..p].to_vec());
if at(p) != 0 {
p += 1;
}
}
while at(p) != 0 && is_space(at(p)) {
p += 1;
}
if at(p) == 0 {
break;
}
}
out.extend(rest);
out
}
pub(crate) fn abbrevmatch(
matchstring: &[u8],
abbrev: &[u8],
case_sensitive: bool,
minmatch: usize,
) -> bool {
let mut cnt = 0;
let (mut mi, mut ai) = (0, 0);
while mi < matchstring.len() && ai < abbrev.len() {
cnt += 1;
let (a, b) = (matchstring[mi], abbrev[ai]);
let same = if case_sensitive {
a == b
} else {
a.eq_ignore_ascii_case(&b)
};
if !same {
return false;
}
mi += 1;
ai += 1;
}
if cnt < minmatch {
return false;
}
ai >= abbrev.len()
}
pub(crate) fn read_num_string(numstring: &[u8]) -> (u64, Option<&'static str>) {
if numstring.first().is_none_or(|b| !b.is_ascii_digit()) {
return (u64::MAX, Some(m::NUM_NOT_DIGIT));
}
if numstring.len() > 8 {
return (u64::MAX, Some(m::NUM_TOO_LONG));
}
let digits = numstring.iter().take_while(|b| b.is_ascii_digit()).count();
let num: u64 = numstring[..digits]
.iter()
.fold(0, |n, &d| n * 10 + u64::from(d - b'0'));
if digits == numstring.len() {
return (num, None);
}
if numstring.len() > digits + 1 {
return (u64::MAX, None);
}
let mult: u64 = match numstring[digits].to_ascii_uppercase() {
b'K' => 1 << 10,
b'M' => 1 << 20,
b'G' => 1 << 30,
b'T' => 1 << 40,
_ => return (u64::MAX, None),
};
(num.wrapping_mul(mult), None)
}
pub(crate) fn dostime(y: i64, n: i64, d: i64, h: i64, mi: i64, s: i64) -> u64 {
if y < 1980 {
return 0x0021_0000;
}
(((y - 1980) as u64) << 25)
| ((n as u64) << 21)
| ((d as u64) << 16)
| ((h as u64) << 11)
| ((mi as u64) << 5)
| ((s as u64) >> 1)
}
fn scan_date(s: &[u8], fmt: [(usize, Option<u8>); 3]) -> (usize, [i64; 3]) {
let mut vals = [0i64; 3];
let mut p = 0usize;
for (i, &(width, sep)) in fmt.iter().enumerate() {
while p < s.len() && is_space(s[p]) {
p += 1;
}
let mut neg = false;
let mut n = 0usize;
if p < s.len() && matches!(s[p], b'+' | b'-') && n < width {
neg = s[p] == b'-';
p += 1;
n += 1;
}
let digits_from = p;
let mut v: i64 = 0;
while p < s.len() && s[p].is_ascii_digit() && n < width {
v = v * 10 + i64::from(s[p] - b'0');
p += 1;
n += 1;
}
if p == digits_from {
return (i, vals);
}
vals[i] = if neg { -v } else { v };
if let Some(c) = sep {
if p < s.len() && s[p] == c {
p += 1;
} else {
return (i + 1, vals);
}
}
}
(3, vals)
}
fn read_date(value: &[u8]) -> Option<u64> {
let iso = scan_date(value, [(4, Some(b'-')), (2, Some(b'-')), (2, None)]);
let (yyyy, mm, dd);
if iso.0 == 3 {
(yyyy, mm, dd) = (iso.1[0], iso.1[1], iso.1[2]);
} else {
let us = scan_date(value, [(2, None), (2, None), (4, None)]);
if us.0 != 3 {
return None;
}
(mm, dd, yyyy) = (us.1[0], us.1[1], us.1[2]);
}
if !(1..=12).contains(&mm) || !(1..=31).contains(&dd) {
return None;
}
Some(dostime(yyyy, mm, dd, 0, 0, 0))
}
pub(crate) fn ziptyp(s: &[u8], adjust: bool) -> Vec<u8> {
let mut t = s.to_vec();
if adjust {
return t;
}
let comp = s.iter().rposition(|&b| b == b'/').map_or(0, |i| i + 1);
if !s[comp..].contains(&b'.') {
t.extend_from_slice(b".zip");
}
t
}
pub(crate) fn getnam(r: &mut dyn BufRead) -> Option<Vec<u8>> {
const GETNAM_MAX: usize = 9000;
let mut bytes = r.bytes().map_while(Result::ok);
let mut c = bytes.next();
while matches!(c, Some(b'\n' | b'\r')) {
c = bytes.next();
}
c?;
let mut name = Vec::new();
while let Some(b) = c {
if b == b'\n' || b == b'\r' {
break;
}
if name.len() >= GETNAM_MAX {
return None;
}
name.push(b);
c = bytes.next();
}
Some(name)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Action {
Delete,
Add,
Update,
Freshen,
Archive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Method {
Best,
Store,
Deflate,
Bzip2,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Filter {
pub(crate) flag: u8,
pub(crate) pattern: Vec<u8>,
}
#[derive(Debug)]
pub(crate) struct Options {
pub(crate) action: Action,
pub(crate) comadd: bool,
pub(crate) zipedit: bool,
pub(crate) latest: bool,
pub(crate) before: u64,
pub(crate) after: u64,
pub(crate) test: bool,
pub(crate) unzip_path: Option<Vec<u8>>,
pub(crate) tempdir: bool,
pub(crate) tempath: Option<Vec<u8>>,
pub(crate) junk_sfx: bool,
pub(crate) recurse: u8,
pub(crate) dispose: bool,
pub(crate) pathput: bool,
pub(crate) method: Method,
pub(crate) dosify: bool,
pub(crate) verbose: u32,
pub(crate) fix: u8,
pub(crate) filesync: bool,
pub(crate) adjust: bool,
pub(crate) level: u8,
pub(crate) translate_eol: u8,
pub(crate) no_wild: bool,
pub(crate) allow_regex: bool,
pub(crate) wild_stop_at_dir: bool,
pub(crate) dot_size: i64,
pub(crate) dot_count: i64,
pub(crate) display_counts: bool,
pub(crate) display_bytes: bool,
pub(crate) display_globaldots: bool,
pub(crate) display_volume: bool,
pub(crate) display_usize: bool,
pub(crate) logall: bool,
pub(crate) logfile_path: Option<Vec<u8>>,
pub(crate) logfile_append: bool,
pub(crate) dirnames: bool,
pub(crate) diff_mode: bool,
pub(crate) linkput: bool,
pub(crate) noisy: bool,
pub(crate) extra_fields: u8,
pub(crate) use_descriptors: bool,
pub(crate) zip_to_stdout: bool,
pub(crate) allow_empty_archive: bool,
pub(crate) copy_only: bool,
pub(crate) allow_fifo: bool,
pub(crate) show_files: u8,
pub(crate) force_zip64: i8,
pub(crate) utf8_force: bool,
pub(crate) unicode_escape_all: bool,
pub(crate) unicode_mismatch: u8,
pub(crate) special: Option<Vec<u8>>,
pub(crate) key: Option<crate::compat::unzip::ttyio::Password>,
pub(crate) key_needed: bool,
pub(crate) bad_open_is_error: bool,
pub(crate) zipfile: Option<Vec<u8>>,
pub(crate) in_path: Option<Vec<u8>>,
pub(crate) out_path: Option<Vec<u8>>,
pub(crate) have_out: bool,
pub(crate) split_method: i8,
pub(crate) split_size: u64,
pub(crate) split_bell: bool,
pub(crate) noisy_splits: bool,
pub(crate) show_what_doing: bool,
pub(crate) show_args: bool,
pub(crate) show_options: bool,
pub(crate) filters: Vec<Filter>,
pub(crate) names: Vec<Vec<u8>>,
pub(crate) grow: bool,
pub(crate) names_from_stdin: bool,
pub(crate) comment_from_stderr: bool,
pub(crate) kk: u8,
pub(crate) first_listarg: i64,
pub(crate) filter_mode: bool,
pub(crate) args: Vec<Vec<u8>>,
}
impl Options {
pub(crate) fn new() -> Options {
Options {
action: Action::Add,
comadd: false,
zipedit: false,
latest: false,
before: 0,
after: 0,
test: false,
unzip_path: None,
tempdir: false,
tempath: None,
junk_sfx: false,
recurse: 0,
dispose: false,
pathput: true,
method: Method::Best,
dosify: false,
verbose: 0,
fix: 0,
filesync: false,
adjust: false,
level: 6,
translate_eol: 0,
no_wild: false,
allow_regex: true,
wild_stop_at_dir: false,
dot_size: 0,
dot_count: 0,
display_counts: false,
display_bytes: false,
display_globaldots: false,
display_volume: false,
display_usize: false,
logall: false,
logfile_path: None,
logfile_append: false,
dirnames: true,
diff_mode: false,
linkput: false,
noisy: true,
extra_fields: 1,
use_descriptors: false,
zip_to_stdout: false,
allow_empty_archive: false,
copy_only: false,
allow_fifo: false,
show_files: 0,
force_zip64: -1,
utf8_force: false,
unicode_escape_all: false,
unicode_mismatch: 1,
special: Some(b".Z:.zip:.zoo:.arc:.lzh:.arj".to_vec()),
key: None,
key_needed: false,
bad_open_is_error: false,
zipfile: None,
in_path: None,
out_path: None,
have_out: false,
split_method: 0,
split_size: 0,
split_bell: false,
noisy_splits: false,
show_what_doing: false,
show_args: false,
show_options: false,
filters: Vec::new(),
names: Vec::new(),
grow: false,
names_from_stdin: false,
comment_from_stderr: false,
kk: 0,
first_listarg: 0,
filter_mode: false,
args: Vec::new(),
}
}
}
pub(crate) fn help(z: &mut Zr) {
let origin = flavour::origin(z.flavour);
let version = env!("CARGO_PKG_VERSION");
let mut text = Vec::new();
text.extend_from_slice(m::ZIP_HELP_PRE.as_bytes());
printf(
&mut text,
m::ZIP_HELP_HEADER,
&[version.as_bytes(), origin.as_bytes()],
);
text.extend_from_slice(m::ZIP_HELP_POST.as_bytes());
z.stdout_write(&text);
}
fn help_extended(z: &mut Zr) {
let mut text = String::from(m::ZIP_HELP2_HEAD);
if flavour::help2_brace_line(z.flavour) {
text.push_str(m::ZIP_HELP2_UBUNTU_LINE);
}
text.push_str(m::ZIP_HELP2_TAIL);
z.stdout_write(text.as_bytes());
}
fn license(z: &mut Zr) {
z.stdout_write(m::ZIP_LICENSE.as_bytes());
}
fn version_text(f: Flavour, name: &str, program: &str) -> Vec<u8> {
let version = env!("CARGO_PKG_VERSION");
let mut text = Vec::new();
text.extend_from_slice(flavour::version_pre(f, name).as_bytes());
printf(
&mut text,
m::VERSION_BANNER,
&[
program.as_bytes(),
version.as_bytes(),
flavour::origin(f).as_bytes(),
],
);
text.extend_from_slice(flavour::version_post(f, name).as_bytes());
text
}
pub(crate) fn version_info(z: &mut Zr) {
let mut text = version_text(z.flavour, "zip", "Zip");
text.extend_from_slice(m::ENV_HEADER.as_bytes());
for name in m::ENV_NAMES {
let v = (z.env)(name)
.map(|v| v.as_encoded_bytes().to_vec())
.filter(|v| !v.is_empty())
.unwrap_or_else(|| m::ENV_NONE.as_bytes().to_vec());
text.extend(std::iter::repeat_n(
b' ',
16usize.saturating_sub(name.len()),
));
text.extend_from_slice(name.as_bytes());
text.extend_from_slice(b": ");
text.extend_from_slice(&v);
text.push(b'\n');
}
z.stdout_write(&text);
}
fn show_options(z: &mut Zr, table: &[OptionSpec]) {
fn pad(out: &mut Vec<u8>, s: &str, width: usize) {
out.extend_from_slice(s.as_bytes());
for _ in s.len()..width {
out.push(b' ');
}
}
let mut out = Vec::new();
out.extend_from_slice(m::AVAILABLE_OPTIONS.as_bytes());
for words in [m::SO_HEADINGS, m::SO_UNDERLINES] {
out.push(b' ');
pad(&mut out, words[0], 2);
out.extend_from_slice(b" ");
pad(&mut out, words[1], 18);
out.push(b' ');
pad(&mut out, words[2], 4);
out.push(b' ');
pad(&mut out, words[3], 3);
out.push(b' ');
pad(&mut out, words[4], 30);
out.push(b'\n');
}
for s in table {
out.push(b' ');
pad(&mut out, s.short, 2);
out.extend_from_slice(b" ");
pad(&mut out, s.long, 18);
out.push(b' ');
pad(
&mut out,
match s.value {
ValueType::No => "",
ValueType::Required => m::SO_REQ,
ValueType::Optional => m::SO_OPT,
ValueType::List => m::SO_LIST,
ValueType::OneChar => m::SO_CHAR,
ValueType::Number => m::SO_NUM,
},
4,
);
out.push(b' ');
pad(&mut out, if s.negatable { m::SO_NEG } else { "" }, 3);
out.push(b' ');
pad(&mut out, s.name, 30);
out.push(b'\n');
}
z.stdout_write(&out);
}
pub(crate) fn finish(z: &mut Zr, e: i32) -> Exit {
if z.o.latest && z.o.zipfile.as_deref().is_some_and(|f| f != b"-") {
z.zipwarn(m::ZIP_EMPTY_NO_LATEST.as_bytes(), b"");
}
Exit { code: e }
}
fn add_filter(z: &mut Zr, flag: u8, pattern: Vec<u8>) -> Result<(), Exit> {
if pattern.first() == Some(&b'@') {
if pattern.len() == 1 {
return Err(z.ziperr(ZE_PARMS, m::MISSING_FILE_AFTER_AT.as_bytes()));
}
let path = z.host.path(&pattern[1..]);
let file = match std::fs::File::open(&path) {
Ok(f) => f,
Err(e) => {
let mut why = Vec::new();
printf(&mut why, m::PATTERN_FILE, &[&[flag], &pattern]);
return Err(z.ziperr_os(ZE_OPEN, &why, &e));
}
};
let mut r = io::BufReader::new(z.host.track(&path, file));
while let Some(p) = getnam(&mut r) {
let Some(pattern) = scan::ex2in(&p, z.o.pathput, z.o.dosify) else {
return Err(scan::crashed(z));
};
z.o.filters.push(Filter { flag, pattern });
}
} else {
let Some(pattern) = scan::ex2in(&pattern, z.o.pathput, z.o.dosify) else {
return Err(scan::crashed(z));
};
z.o.filters.push(Filter { flag, pattern });
}
Ok(())
}
fn zipstdout(z: &mut Zr) -> Result<(), Exit> {
z.mesg_err = true;
if z.host.stdout_is_tty() {
return Err(z.ziperr(ZE_PARMS, m::CANNOT_WRITE_TO_TERMINAL.as_bytes()));
}
z.o.zipfile = Some(b"-".to_vec());
Ok(())
}
fn parms(z: &mut Zr, text: &[u8]) -> Exit {
z.ziperr(ZE_PARMS, text)
}
fn val(v: Option<Vec<u8>>) -> Vec<u8> {
v.unwrap_or_default()
}
pub(crate) fn parse_zip(z: &mut Zr, argv0: &[u8], rest: &[Vec<u8>]) -> Result<(), Exit> {
let table = table(z.flavour);
let mut args: Vec<Vec<u8>> = Vec::with_capacity(rest.len() + 1);
args.push(argv0.to_vec());
args.extend(rest.iter().cloned());
if args.len() == 1 && z.host.stdout_is_tty() {
help(z);
return Err(Exit { code: ZE_OK });
} else if args.len() == 2
&& args[1] == b"-v"
&& (z.host.stdout_is_tty() || z.host.stdin_is_tty())
{
version_info(z);
return Err(Exit { code: ZE_OK });
}
let mut args = envargs(args, z.env, "ZIPOPT", "ZIP");
z.o.args = args.clone();
let mut st = GetOpt::new();
loop {
let got = match get_option(&table, &mut args, &mut st) {
Ok(g) => g,
Err(text) => return Err(parms(z, &text)),
};
if got.id == 0 {
break;
}
let negated = got.negated;
let value = got.value;
match (u8::try_from(got.id).ok(), got.id) {
(Some(b'0'), _) => {
z.o.method = Method::Store;
z.o.level = 0;
}
(Some(c @ b'1'..=b'9'), _) => z.o.level = c - b'0',
(Some(b'A'), _) => z.o.adjust = true,
(Some(b'b'), _) => {
z.o.tempdir = true;
z.o.tempath = value;
}
(Some(b'c'), _) => z.o.comadd = true,
(Some(b'd'), _) => {
if z.o.action != Action::Add {
return Err(z.ziperr(ZE_PARMS, m::JUST_ONE_ACTION.as_bytes()));
}
z.o.action = Action::Delete;
}
(_, o::DB) => z.o.display_bytes = !negated,
(_, o::DC) => z.o.display_counts = !negated,
(_, o::DD) => {
z.o.display_globaldots = false;
if negated {
z.o.dot_count = 0;
} else {
if z.o.dot_count == 0 {
z.o.dot_size = 10 * 0x10_0000;
}
z.o.dot_count = -1;
}
}
(_, o::DG) => {
if negated {
z.o.display_globaldots = false;
} else {
z.o.display_globaldots = true;
if z.o.dot_count == 0 {
z.o.dot_size = 10 * 0x10_0000;
}
z.o.dot_count = -1;
}
}
(_, o::DS) => {
let v = val(value);
if v.is_empty() {
z.o.dot_size = 10 * 0x10_0000;
} else {
let (n, warn) = read_num_string(&v);
if let Some(w) = warn {
z.zipwarn(w.as_bytes(), &v);
}
let mut size = n as i64;
if size == -1 {
let mut e = Vec::new();
printf(&mut e, m::DS_BAD_SIZE, &[&v]);
return Err(z.ziperr(ZE_PARMS, &e));
}
if size < 0x400 {
size = size.wrapping_mul(0x10_0000);
} else if size < 0x400 * 32 {
let mut e = Vec::new();
printf(&mut e, m::DS_TOO_SMALL, &[&v]);
return Err(z.ziperr(ZE_PARMS, &e));
}
z.o.dot_size = size;
}
z.o.dot_count = -1;
}
(_, o::DU) => z.o.display_usize = !negated,
(_, o::DV) => z.o.display_volume = !negated,
(Some(b'D'), _) => z.o.dirnames = false,
(_, o::DF_CAP) => {
z.o.diff_mode = true;
z.o.allow_empty_archive = true;
}
(Some(b'e'), _) => {
z.o.key = None;
z.o.key_needed = true;
}
(Some(b'F'), _) => z.o.fix = 1,
(_, o::FF) => z.o.fix = 2,
(_, o::FI) => z.o.allow_fifo = !negated,
(_, o::FS) => z.o.filesync = true,
(Some(b'f'), _) => {
if z.o.action != Action::Add {
return Err(z.ziperr(ZE_PARMS, m::JUST_ONE_ACTION.as_bytes()));
}
z.o.action = Action::Freshen;
}
(Some(b'g'), _) => z.o.grow = true,
(Some(b'h'), _) => {
help(z);
return Err(finish(z, ZE_OK));
}
(_, o::H2) => {
help_extended(z);
return Err(finish(z, ZE_OK));
}
(Some(b'j'), _) => z.o.pathput = false,
(Some(b'J'), _) => z.o.junk_sfx = true,
(Some(b'k'), _) => z.o.dosify = true,
(Some(b'l'), _) => z.o.translate_eol = 1,
(_, o::LL) => z.o.translate_eol = 2,
(_, o::LF) => z.o.logfile_path = value,
(_, o::LA) => z.o.logfile_append = !negated,
(_, o::LI) => z.o.logall = !negated,
(Some(b'L'), _) => {
license(z);
return Err(finish(z, ZE_OK));
}
(Some(b'm'), _) => z.o.dispose = true,
(_, o::MM) => return Err(z.ziperr(ZE_PARMS, m::MM_NOT_SUPPORTED.as_bytes())),
(_, o::MM_CAP) => z.o.bad_open_is_error = true,
(Some(b'n'), _) => z.o.special = value,
(_, o::NW) => z.o.no_wild = true,
(Some(b'o'), _) => z.o.latest = true,
(Some(b'O'), _) => {
z.o.out_path = Some(ziptyp(&val(value), z.o.adjust));
z.o.have_out = true;
}
(Some(b'p'), _) => {}
(Some(b'P'), _) => {
z.o.key = value.map(crate::compat::unzip::ttyio::Password::from_vec);
z.o.key_needed = false;
}
(Some(b'q'), _) => {
z.o.noisy = false;
if z.o.verbose > 0 {
z.o.verbose -= 1;
}
}
(Some(b'r'), _) => {
if z.o.recurse == 2 {
return Err(z.ziperr(ZE_PARMS, m::NOT_BOTH_R.as_bytes()));
}
z.o.recurse = 1;
}
(Some(b'R'), _) => {
if z.o.recurse == 1 {
return Err(z.ziperr(ZE_PARMS, m::NOT_BOTH_R.as_bytes()));
}
z.o.recurse = 2;
}
(_, o::RE) => z.o.allow_regex = true,
(_, o::SC) => z.o.show_args = true,
(_, o::SD) => z.o.show_what_doing = true,
(_, o::SF) => z.o.show_files = if negated { 2 } else { 1 },
(_, o::SO) => z.o.show_options = true,
(_, o::SU) => z.o.show_files = if negated { 4 } else { 3 },
(_, o::SU_CAP) => z.o.show_files = if negated { 6 } else { 5 },
(Some(b's'), _) => {
let v = val(value);
if v == b"-" {
z.o.split_method = -1;
} else {
let (n, warn) = read_num_string(&v);
if let Some(w) = warn {
z.zipwarn(w.as_bytes(), &v);
}
z.o.split_size = n;
if z.o.split_size == u64::MAX {
let mut e = Vec::new();
printf(&mut e, m::BAD_SPLIT_SIZE, &[&v]);
return Err(z.ziperr(ZE_PARMS, &e));
}
if z.o.split_size == 0 {
z.o.split_method = -1;
} else {
if z.o.split_method == 0 {
z.o.split_method = 1;
}
if z.o.split_size < 0x400 {
z.o.split_size = z.o.split_size.wrapping_mul(0x10_0000);
}
if z.o.split_size < 0x400 * 64 {
let mut e = Vec::new();
printf(&mut e, m::SPLIT_TOO_SMALL, &[&v]);
return Err(z.ziperr(ZE_PARMS, &e));
}
}
}
}
(_, o::SB) => z.o.split_bell = true,
(_, o::SP) => {
z.o.use_descriptors = true;
z.o.split_method = 2;
}
(_, o::SV) => z.o.noisy_splits = true,
(Some(b't'), _) => match read_date(&val(value)) {
Some(t) => z.o.before = t,
None => return Err(z.ziperr(ZE_PARMS, m::BAD_T_DATE.as_bytes())),
},
(_, o::TT) => match read_date(&val(value)) {
Some(t) => z.o.after = t,
None => return Err(z.ziperr(ZE_PARMS, m::BAD_TT_DATE.as_bytes())),
},
(Some(b'T'), _) => z.o.test = true,
(_, o::TT_CAP) => z.o.unzip_path = value,
(Some(b'U'), _) => {
if z.o.action != Action::Add {
return Err(z.ziperr(ZE_PARMS, m::JUST_ONE_ACTION.as_bytes()));
}
z.o.action = Action::Archive;
}
(_, o::UN) => {
let v = val(value);
if abbrevmatch(b"quit", &v, false, 1) {
z.o.unicode_mismatch = 0;
} else if abbrevmatch(b"warn", &v, false, 1) {
z.o.unicode_mismatch = 1;
} else if abbrevmatch(b"ignore", &v, false, 1) {
z.o.unicode_mismatch = 2;
} else if abbrevmatch(b"no", &v, false, 1) {
z.o.unicode_mismatch = 3;
} else if abbrevmatch(b"escape", &v, false, 1) {
z.o.unicode_escape_all = true;
} else if abbrevmatch(b"UTF8", &v, false, 1) {
z.o.utf8_force = true;
} else {
z.zipwarn(m::UN_MUST_BE.as_bytes(), &v);
return Err(z.ziperr(ZE_PARMS, m::UN_BAD_VALUE.as_bytes()));
}
}
(Some(b'u'), _) => {
if z.o.action != Action::Add {
return Err(z.ziperr(ZE_PARMS, m::JUST_ONE_ACTION.as_bytes()));
}
z.o.action = Action::Update;
}
(Some(b'v'), _) | (_, o::VE) => {
if got.id == o::VE || (args.len() == 2 && args[1].len() == 2) {
version_info(z);
return Err(finish(z, ZE_OK));
}
z.o.noisy = true;
z.o.verbose += 1;
}
(_, o::WS) => z.o.wild_stop_at_dir = true,
(Some(b'i' | b'x'), _) => {
if got.id == u32::from(b'i') {
z.o.allow_empty_archive = true;
}
add_filter(z, got.id as u8, val(value))?;
}
(Some(b'y'), _) => z.o.linkput = true,
(Some(b'z'), _) => z.o.zipedit = true,
(Some(b'Z'), _) => {
let v = val(value);
if abbrevmatch(b"deflate", &v, false, 1) {
z.o.method = Method::Deflate;
} else if abbrevmatch(b"store", &v, false, 1) {
z.o.method = Method::Store;
} else if abbrevmatch(b"bzip2", &v, false, 1) {
if flavour::bzip2_support(z.flavour) {
z.o.method = Method::Bzip2;
} else {
return Err(z.ziperr(ZE_COMPERR, m::Z_BZIP2_NOT_ENABLED.as_bytes()));
}
} else {
let methods = if flavour::bzip2_support(z.flavour) {
m::Z_METHODS_BZIP2
} else {
m::Z_METHODS_PLAIN
};
z.zipwarn(methods.as_bytes(), b"");
z.zipwarn(m::Z_UNKNOWN_FOUND.as_bytes(), &v);
return Err(z.ziperr(ZE_PARMS, m::Z_UNKNOWN.as_bytes()));
}
}
(Some(b'@'), _) => {
z.o.names_from_stdin = true;
z.o.comment_from_stderr = true;
}
(Some(b'X'), _) => z.o.extra_fields = if negated { 2 } else { 0 },
(_, o::DES) => z.o.use_descriptors = true,
(_, o::Z64) => z.o.force_zip64 = if negated { 0 } else { 1 },
(_, o::NON_OPTION_ARG) => {
non_option_arg(z, val(value), st.argnum)?;
}
(_, id) => {
let mut e = Vec::new();
printf(&mut e, m::NO_SUCH_OPTION_ID, &[id.to_string().as_bytes()]);
return Err(z.ziperr(ZE_PARMS, &e));
}
}
}
if z.o.key_needed {
crate::compat::host::install_zip_abort(&z.host, m::INTERRUPTED_ABORTING);
let line = z.mesg_line_started;
z.set_line(line);
let Some(key) = super::ttyio::getp(z, m::ENTER_PASSWORD.as_bytes()) else {
return Err(z.ziperr(ZE_PARMS, m::STDERR_NOT_TTY.as_bytes()));
};
if key.is_empty_answer() {
return Err(z.ziperr(ZE_PARMS, m::ZERO_LENGTH_PASSWORD.as_bytes()));
}
let Some(e) = super::ttyio::getp(z, m::VERIFY_PASSWORD.as_bytes()) else {
return Err(z.ziperr(ZE_PARMS, m::STDERR_NOT_TTY.as_bytes()));
};
if key.key() != e.key() {
return Err(z.ziperr(ZE_PARMS, m::PASSWORD_VERIFICATION_FAILED.as_bytes()));
}
z.o.key = Some(key);
}
if let Some(key) = &z.o.key
&& key.key().is_empty()
{
return Err(z.ziperr(ZE_PARMS, m::ZERO_LENGTH_PASSWORD.as_bytes()));
}
if z.o.show_what_doing {
z.mesg_write(m::SD_COMMAND_LINE_READ.as_bytes());
}
if z.o.show_args {
z.mesg_write(m::COMMAND_LINE.as_bytes());
for a in &args {
let mut line = Vec::new();
printf(&mut line, m::COMMAND_LINE_ARG, &[a]);
z.mesg_write(&line);
}
z.mesg_write(b"\n");
return Err(z.ziperr(ZE_ABORT, m::SHOW_COMMAND_LINE.as_bytes()));
}
if z.o.show_options {
show_options(z, &table);
return Err(finish(z, ZE_OK));
}
let logged = redact_password(&table, z.o.args.clone());
debug_assert_eq!(logged.len(), args.len());
open_log(z, &logged)?;
checks(z)
}
pub(crate) const LOGGED_PASSWORD: &[u8] = b"********";
fn redact_password(table: &[OptionSpec], mut args: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
let mut st = GetOpt::new();
while let Ok(got) = get_option(table, &mut args, &mut st) {
if got.id == 0 {
break;
}
if got.id != ch('P') {
continue;
}
let Some(value) = got.value else { continue };
if st.optchar == SKIP_VALUE_ARG {
if let Some(a) = usize::try_from(st.argnum + 1)
.ok()
.and_then(|i| args.get_mut(i))
{
let mut old = std::mem::replace(a, LOGGED_PASSWORD.to_vec());
crate::compat::unzip::ttyio::wipe(&mut old);
}
} else if let Some(a) = usize::try_from(st.argnum)
.ok()
.and_then(|i| args.get_mut(i))
&& a.ends_with(&value)
{
let at = a.len() - value.len();
crate::compat::unzip::ttyio::wipe(&mut a[at..]);
a.truncate(at);
a.extend_from_slice(LOGGED_PASSWORD);
}
drop(crate::compat::unzip::ttyio::Password::from_vec(value));
}
args
}
fn non_option_arg(z: &mut Zr, value: Vec<u8>, argnum: i64) -> Result<(), Exit> {
if value == b"--" && !z.seen_doubledash {
z.seen_doubledash = true;
if z.o.kk == 0 {
return Err(z.ziperr(ZE_PARMS, m::DOUBLEDASH_BEFORE_NAME.as_bytes()));
}
return Ok(());
}
if z.o.kk == 6 {
add_filter(z, b'R', value)?;
if z.o.first_listarg == 0 {
z.o.first_listarg = argnum;
}
return Ok(());
}
match z.o.kk {
0 => {
if value == b"-" {
zipstdout(z)?;
} else {
z.o.zipfile = Some(ziptyp(&value, z.o.adjust));
}
let zipfile = z.o.zipfile.clone().unwrap_or_default();
if z.o.show_what_doing {
let mut line = Vec::new();
printf(&mut line, m::SD_ZIPFILE_NAME, &[&zipfile]);
z.mesg_write(&line);
}
if z.o.in_path.is_none() {
z.o.in_path = Some(zipfile.clone());
}
if z.o.out_path.is_none() {
z.o.out_path = Some(zipfile);
}
z.o.kk = 3;
if z.o.names_from_stdin {
let mut input = z.host.stdin();
while let Some(pp) = getnam(&mut input) {
z.o.kk = 4;
if z.o.recurse == 2 {
add_filter(z, b'R', pp)?;
} else {
z.o.names.push(pp);
}
}
z.o.names_from_stdin = false;
}
if z.o.recurse == 2 {
z.o.kk = 6;
}
}
_ => {
if z.o.names_from_stdin && value == b"-" {
return Err(z.ziperr(ZE_PARMS, m::STDIN_BOTH.as_bytes()));
}
z.o.names.push(value);
if z.o.kk == 3 {
z.o.first_listarg = argnum;
z.o.kk = 4;
}
}
}
Ok(())
}
fn open_log(z: &mut Zr, args: &[Vec<u8>]) -> Result<(), Exit> {
let Some(mut path) = z.o.logfile_path.clone() else {
z.o.logall = false;
return Ok(());
};
let lastp = path.iter().rposition(|&b| b == b'/').unwrap_or(0);
if !path[lastp..].contains(&b'.') {
path.extend_from_slice(b".log");
z.o.logfile_path = Some(path.clone());
}
let mut opts = std::fs::OpenOptions::new();
if z.o.logfile_append {
opts.append(true).create(true);
} else {
opts.write(true).create(true).truncate(true);
}
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
opts.mode(0o600).custom_flags(libc::O_NOFOLLOW);
}
let file = match opts.open(z.host.path(&path)) {
Ok(f) => f,
Err(_) => {
let mut e = Vec::new();
printf(&mut e, m::COULD_NOT_OPEN_LOG, &[&path]);
return Err(z.ziperr(ZE_PARMS, &e));
}
};
z.log = Some(Box::new(file));
let mut head = Vec::new();
head.extend_from_slice(m::LOG_SEPARATOR.as_bytes());
printf(
&mut head,
m::LOG_OPENED,
&[super::asctime_now(z).as_bytes()],
);
head.extend_from_slice(m::LOG_COMMAND_LINE.as_bytes());
for a in args.iter().skip(1) {
let fmt = if a.iter().any(|&b| is_space(b)) {
m::LOG_ARG_QUOTED
} else {
m::LOG_ARG
};
printf(&mut head, fmt, &[a]);
}
head.extend_from_slice(m::LOG_ARGS_END.as_bytes());
z.log_write(&head);
Ok(())
}
fn checks(z: &mut Zr) -> Result<(), Exit> {
if z.o.split_method != 0
&& let Some(out) = &z.o.out_path
{
let ext_ok = out.len() >= 4 && out[out.len() - 4..].eq_ignore_ascii_case(b".zip");
if !ext_ok {
return Err(z.ziperr(ZE_PARMS, m::SPLIT_NEEDS_ZIP.as_bytes()));
}
}
if z.o.verbose > 0 && z.o.dot_size == 0 && z.o.dot_count == 0 {
z.o.dot_size = 10 * 0x10_0000;
}
if z.o.have_out && z.o.kk == 3 {
z.o.copy_only = true;
z.o.action = Action::Archive;
}
if z.o.have_out && z.o.in_path.is_none() {
z.mesg_write(b"\n");
return Err(z.ziperr(ZE_ABORT, m::ABORTING.as_bytes()));
}
if z.o.have_out && z.o.in_path == z.o.out_path {
let mut e = Vec::new();
printf(
&mut e,
m::OUT_PATH_DIFFERENT,
&[z.o.out_path.as_deref().unwrap_or_default()],
);
return Err(z.ziperr(ZE_PARMS, &e));
}
if z.o.fix != 0
&& z.o.have_out
&& let (Some(i), Some(o)) = (&z.o.in_path, &z.o.out_path)
&& let (Ok(a), Ok(b)) = (
std::fs::metadata(z.host.path(i)),
std::fs::metadata(z.host.path(o)),
)
&& crate::compat::host::same_file(&a, &b) == Some(true)
{
let mut e = Vec::new();
printf(&mut e, m::OUT_PATH_DIFFERENT, &[o]);
return Err(z.ziperr(ZE_PARMS, &e));
}
if z.o.fix != 0 && z.o.diff_mode {
return Err(z.ziperr(ZE_PARMS, m::DIFF_WITH_FIX.as_bytes()));
}
if z.o.action == Action::Archive && !z.o.have_out && z.o.show_files == 0 {
return Err(z.ziperr(ZE_PARMS, m::COPY_NEEDS_OUT.as_bytes()));
}
if z.o.fix != 0 && !z.o.have_out {
z.zipwarn(
m::FIX_NEEDS_OUT_WARN.as_bytes(),
m::FIX_NEEDS_OUT_EXAMPLE.as_bytes(),
);
return Err(z.ziperr(ZE_PARMS, m::FIX_NEEDS_OUT.as_bytes()));
}
if z.o.fix != 0 && !z.o.copy_only {
return Err(z.ziperr(ZE_PARMS, m::FIX_NO_OTHER_ACTIONS.as_bytes()));
}
if !z.o.have_out && z.o.diff_mode {
return Err(z.ziperr(ZE_PARMS, m::DIFF_NEEDS_OUT.as_bytes()));
}
if z.o.diff_mode && matches!(z.o.action, Action::Archive | Action::Delete) {
return Err(z.ziperr(ZE_PARMS, m::DIFF_WITH_D_OR_U.as_bytes()));
}
if z.o.action != Action::Archive
&& (z.o.recurse == 2 || !z.o.filters.is_empty())
&& z.o.first_listarg == 0
&& z.o.names.is_empty()
&& (z.o.kk < 3 || !matches!(z.o.action, Action::Update | Action::Freshen))
{
return Err(z.ziperr(ZE_PARMS, m::NOTHING_TO_SELECT.as_bytes()));
}
if z.o.kk < 3 {
zipstdout(z)?;
z.o.filter_mode = true;
z.o.kk = 4;
if z.o.names_from_stdin {
return Err(z.ziperr(ZE_PARMS, m::DASH_AND_AT.as_bytes()));
}
}
if z.o.zipfile.as_deref() == Some(b"-") {
if z.o.show_what_doing {
z.mesg_write(m::SD_ZIPPING_TO_STDOUT.as_bytes());
}
z.o.zip_to_stdout = true;
}
if z.o.special.is_none() {
return Err(z.ziperr(ZE_PARMS, m::MISSING_SUFFIX_LIST.as_bytes()));
}
if z.o.level == 9
|| z.o.special.as_deref() == Some(b";")
|| z.o.special.as_deref() == Some(b":")
{
z.o.special = None;
}
if z.o.action == Action::Delete
&& (z.o.method != Method::Best
|| z.o.dispose
|| z.o.recurse != 0
|| z.o.key.is_some()
|| z.o.comadd
|| z.o.zipedit)
{
z.zipwarn(m::D_INVALID_OPTIONS.as_bytes(), b"");
z.o.method = Method::Best;
z.o.dispose = false;
z.o.recurse = 0;
z.o.key = None;
z.o.comadd = false;
z.o.zipedit = false;
}
if z.o.action == Action::Archive
&& (z.o.method != Method::Best
|| z.o.dispose
|| z.o.recurse != 0
|| z.o.comadd
|| z.o.zipedit)
{
z.zipwarn(m::COPY_MODE_OPTIONS.as_bytes(), b"");
z.o.method = Method::Best;
z.o.dispose = false;
z.o.recurse = 0;
z.o.comadd = false;
z.o.zipedit = false;
}
if z.o.linkput && z.o.dosify {
z.zipwarn(m::Y_WITH_K.as_bytes(), b"");
z.o.linkput = false;
}
if z.o.fix == 1 && z.o.adjust {
z.zipwarn(m::F_WITH_A.as_bytes(), b"");
z.o.fix = 0;
}
if z.o.fix == 2 && z.o.adjust {
z.zipwarn(m::FF_WITH_A.as_bytes(), b"");
z.o.fix = 0;
}
if z.o.test && z.o.zip_to_stdout {
z.o.test = false;
z.zipwarn(m::T_ON_STDOUT.as_bytes(), b"");
}
if z.o.split_method != 0 && (z.o.fix != 0 || z.o.adjust) {
return Err(z.ziperr(ZE_PARMS, m::SPLIT_WITH_FIX.as_bytes()));
}
if z.o.split_method != 0 && (z.o.grow || z.o.zip_to_stdout) {
return Err(z.ziperr(ZE_PARMS, m::SPLIT_WITH_D_OR_STDOUT.as_bytes()));
}
if (z.o.action != Action::Add || z.o.grow) && z.o.filesync {
return Err(z.ziperr(ZE_PARMS, m::FILESYNC_WITH_ACTION.as_bytes()));
}
if (z.o.action != Action::Add || z.o.grow) && z.o.zip_to_stdout {
return Err(z.ziperr(ZE_PARMS, m::ACTION_ON_STDOUT.as_bytes()));
}
if z.o.noisy {
if z.o.fix == 1 {
z.zipmessage(m::FIX_ARCHIVE_F.as_bytes(), b"");
} else if z.o.fix == 2 {
z.zipmessage(m::FIX_ARCHIVE_FF.as_bytes(), b"");
}
}
Ok(())
}
#[derive(Debug, Clone, Default)]
pub(crate) struct CloakOptions {
pub(crate) zipfile: Vec<u8>,
pub(crate) out_path: Option<Vec<u8>>,
pub(crate) tempath: Option<Vec<u8>>,
pub(crate) decrypt: bool,
}
fn cloak_help(z: &mut Zr) {
let mut text = Vec::new();
text.extend_from_slice(m::ZIPCLOAK_HELP_PRE.as_bytes());
printf(
&mut text,
m::ZIPCLOAK_HELP_HEADER,
&[
env!("CARGO_PKG_VERSION").as_bytes(),
flavour::origin(z.flavour).as_bytes(),
],
);
text.extend_from_slice(m::ZIPCLOAK_HELP_POST.as_bytes());
z.stdout_write(&text);
}
fn helper_help(z: &mut Zr, name: &str) {
let (pre, header, post) = if name == "zipnote" {
(
m::ZIPNOTE_HELP_PRE,
m::ZIPNOTE_HELP_HEADER,
m::ZIPNOTE_HELP_POST,
)
} else {
(
m::ZIPSPLIT_HELP_PRE,
m::ZIPSPLIT_HELP_HEADER,
m::ZIPSPLIT_HELP_POST,
)
};
let mut text = Vec::new();
text.extend_from_slice(pre.as_bytes());
printf(
&mut text,
header,
&[
env!("CARGO_PKG_VERSION").as_bytes(),
flavour::origin(z.flavour).as_bytes(),
],
);
text.extend_from_slice(post.as_bytes());
z.stdout_write(&text);
}
fn helper_version(z: &mut Zr, name: &str) {
let program = match name {
"zipcloak" => "ZipCloak",
"zipnote" => "ZipNote",
_ => "ZipSplit",
};
let text = version_text(z.flavour, name, program);
z.stdout_write(&text);
}
fn helper_license(z: &mut Zr, name: &str) {
let mut text = String::from(m::ZIP_LICENSE);
if name == "zipcloak" {
text.push_str(m::ZIPCLOAK_LICENSE_EXTRA);
}
z.stdout_write(text.as_bytes());
}
pub(crate) fn parse_cloak(
z: &mut Zr,
argv0: &[u8],
rest: &[Vec<u8>],
) -> Result<CloakOptions, Exit> {
if rest.is_empty() {
cloak_help(z);
return Err(Exit { code: ZE_OK });
}
z.mesg_err = false;
let table = cloak_table();
let mut args: Vec<Vec<u8>> = vec![argv0.to_vec()];
args.extend(rest.iter().cloned());
let mut o = CloakOptions::default();
let mut zipfile: Option<Vec<u8>> = None;
let mut temp_path = false;
let mut st = GetOpt::new();
loop {
let got = match get_option(&table, &mut args, &mut st) {
Ok(g) => g,
Err(text) => return Err(z.helper_err(ZE_PARMS, &text)),
};
if got.id == 0 {
break;
}
match (u8::try_from(got.id).ok(), got.id) {
(Some(b'b'), _) => {
if temp_path {
return Err(z.helper_err(ZE_PARMS, m::CLOAK_MORE_THAN_ONE_TEMP.as_bytes()));
}
temp_path = true;
o.tempath = got.value;
}
(Some(b'd'), _) => o.decrypt = true,
(Some(b'h'), _) => {
cloak_help(z);
return Err(Exit { code: ZE_OK });
}
(Some(b'L'), _) => {
helper_license(z, "zipcloak");
return Err(Exit { code: ZE_OK });
}
(Some(b'O'), _) => o.out_path = Some(ziptyp(&val(got.value), false)),
(Some(b'v'), _) => {
helper_version(z, "zipcloak");
return Err(Exit { code: ZE_OK });
}
(_, o::NON_OPTION_ARG) => {
let value = val(got.value);
if value == b"-" {
return Err(z.helper_err(ZE_PARMS, m::ZIP_FILE_STDIN.as_bytes()));
} else if zipfile.is_some() {
return Err(z.helper_err(ZE_PARMS, m::ONLY_ONE_ZIP_FILE.as_bytes()));
}
zipfile = Some(ziptyp(&value, false));
}
_ => return Err(z.helper_err(ZE_PARMS, m::UNKNOWN_OPTION.as_bytes())),
}
}
let Some(zipfile) = zipfile else {
return Err(z.helper_err(ZE_PARMS, m::NEED_ZIP_FILE.as_bytes()));
};
o.zipfile = zipfile;
Ok(o)
}
#[derive(Debug, Clone, Default)]
pub(crate) struct NoteOptions {
pub(crate) zipfile: Vec<u8>,
pub(crate) tempath: Option<Vec<u8>>,
pub(crate) write: bool,
}
pub(crate) fn parse_note(z: &mut Zr, rest: &[Vec<u8>]) -> Result<NoteOptions, Exit> {
if rest.is_empty() {
helper_help(z, "zipnote");
return Err(Exit { code: ZE_OK });
}
z.mesg_err = true;
let mut o = NoteOptions::default();
let mut zipfile: Option<Vec<u8>> = None;
let mut k = false;
for arg in rest {
if arg.first() == Some(&b'-') {
if arg.len() == 1 {
return Err(z.helper_err(ZE_PARMS, m::ZIP_FILE_STDIN.as_bytes()));
}
for &c in &arg[1..] {
match c {
b'b' => {
if k {
return Err(z.helper_err(ZE_PARMS, m::USE_B_BEFORE_NAME.as_bytes()));
}
k = true;
}
b'h' => {
helper_help(z, "zipnote");
return Err(Exit { code: ZE_OK });
}
b'l' | b'L' => {
helper_license(z, "zipnote");
return Err(Exit { code: ZE_OK });
}
b'q' => z.o.noisy = false,
b'v' => {
helper_version(z, "zipnote");
return Err(Exit { code: ZE_OK });
}
b'w' => o.write = true,
_ => return Err(z.helper_err(ZE_PARMS, m::UNKNOWN_OPTION.as_bytes())),
}
}
} else if !k {
if zipfile.is_none() {
zipfile = Some(ziptyp(arg, false));
} else {
return Err(z.helper_err(ZE_PARMS, m::ONLY_ONE_ZIP_FILE.as_bytes()));
}
} else {
o.tempath = Some(arg.clone());
k = false;
}
}
let Some(zipfile) = zipfile else {
return Err(z.helper_err(ZE_PARMS, m::NEED_ZIP_FILE.as_bytes()));
};
o.zipfile = zipfile;
Ok(o)
}
#[derive(Debug, Clone)]
pub(crate) struct SplitOptions {
pub(crate) zipfile: Vec<u8>,
pub(crate) tempath: Option<Vec<u8>>,
pub(crate) capacity: u64,
pub(crate) room: i64,
pub(crate) report_only: bool,
pub(crate) index: bool,
pub(crate) pause: bool,
pub(crate) simple: bool,
}
fn atol(s: &[u8]) -> i64 {
let mut p = 0;
while p < s.len() && is_space(s[p]) {
p += 1;
}
let neg = s.get(p) == Some(&b'-');
if matches!(s.get(p), Some(b'-' | b'+')) {
p += 1;
}
let mut v: i64 = 0;
while p < s.len() && s[p].is_ascii_digit() {
let d = i64::from(s[p] - b'0');
v = match v.checked_mul(10).and_then(|v| v.checked_add(d)) {
Some(v) => v,
None => return if neg { i64::MIN } else { i64::MAX },
};
p += 1;
}
if neg { -v } else { v }
}
pub(crate) fn parse_split(z: &mut Zr, rest: &[Vec<u8>]) -> Result<SplitOptions, Exit> {
if rest.is_empty() {
helper_help(z, "zipsplit");
return Err(Exit { code: ZE_OK });
}
z.mesg_err = false;
let mut o = SplitOptions {
zipfile: Vec::new(),
tempath: None,
capacity: 36000,
room: 0,
report_only: false,
index: false,
pause: false,
simple: false,
};
let mut zipfile: Option<Vec<u8>> = None;
let mut k = 0u8;
for arg in rest {
if arg.first() == Some(&b'-') {
if arg.len() == 1 {
return Err(z.helper_err(ZE_PARMS, m::ZIP_FILE_STDIN.as_bytes()));
}
for &c in &arg[1..] {
match c {
b'b' | b'n' | b'r' => {
if k != 0 {
return Err(z.helper_err(ZE_PARMS, m::OPTIONS_SEPARATE.as_bytes()));
}
k = match c {
b'b' => 1,
b'n' => 2,
_ => 3,
};
}
b'h' => {
helper_help(z, "zipsplit");
return Err(Exit { code: ZE_OK });
}
b'i' => o.index = true,
b'l' | b'L' => {
helper_license(z, "zipsplit");
return Err(Exit { code: ZE_OK });
}
b'p' => o.pause = true,
b'q' => z.o.noisy = false,
b's' => o.simple = true,
b't' => o.report_only = true,
b'v' => {
helper_version(z, "zipsplit");
return Err(Exit { code: ZE_OK });
}
_ => return Err(z.helper_err(ZE_PARMS, m::USE_H_FOR_HELP.as_bytes())),
}
}
} else {
match k {
0 => {
if zipfile.is_none() {
zipfile = Some(ziptyp(arg, false));
} else {
return Err(z.helper_err(ZE_PARMS, m::ONLY_ONE_ZIP_FILE.as_bytes()));
}
}
1 => {
o.tempath = Some(arg.clone());
k = 0;
}
2 => {
o.capacity = atol(arg) as u64;
if o.capacity < 100 {
return Err(z.helper_err(ZE_PARMS, m::INVALID_SIZE.as_bytes()));
}
k = 0;
}
_ => {
o.room = atol(arg);
k = 0;
}
}
}
}
let Some(zipfile) = zipfile else {
return Err(z.helper_err(ZE_PARMS, m::NEED_ZIP_FILE.as_bytes()));
};
o.zipfile = zipfile;
Ok(o)
}
#[cfg(test)]
mod tests;