use stuffr::infozip::crypt::{Keys, RAND_HEAD_LEN, crypt_header};
use super::cli::{CloakOptions, ZE_OK, ZE_PARMS};
use super::messages::{self as m, printf};
use super::readzip::{self, CFile, CopyFail, InArchive, ZFile, get_extra_field};
use super::sink::{FileSink, Temp, mkstemp, replace};
use super::unix;
use super::write::set_mode;
use super::zipfile::{self, Bf, LOCHEAD, PutError, ZEntry, ZOut};
use super::zipup::{put_failed, write_failed, zerr};
use super::{Exit, Zr};
use crate::compat::host::{self, Host};
const ZE_EOF: i32 = 2;
const ZE_LOGIC: i32 = 5;
const ZE_BIG: i32 = 6;
const ZE_TEMP: i32 = 10;
const ZE_READ: i32 = 11;
const ZE_NONE: i32 = 12;
const ZE_NAME: i32 = 13;
const ZE_CREAT: i32 = 15;
const ZE_MISS: i32 = -1;
const BUF: usize = 1024;
type Fail = CopyFail;
pub(super) fn fail_exit(z: &mut Zr, f: Fail, h: &[u8]) -> Exit {
match f {
CopyFail::Code(code, err) => zerr(z, code, h, err.as_ref()),
CopyFail::Write(err) => write_failed(z, &err),
CopyFail::Exit(x) => x,
}
}
fn read_exact(f: &mut CFile, n: usize) -> Result<Vec<u8>, Fail> {
let b = f.fread(n);
if b.len() < n {
return Err(CopyFail::Code(
if f.ferror() { ZE_READ } else { ZE_EOF },
None,
));
}
Ok(b)
}
pub(crate) fn run(z: &mut Zr, o: CloakOptions) -> Exit {
host::install_zip_abort(&z.host, m::CLOAK_INTERRUPTED);
z.set_line(false);
let zipfile = o.zipfile.clone();
let out_path = o.out_path.clone().unwrap_or_else(|| zipfile.clone());
z.o.zipfile = Some(zipfile.clone());
z.o.in_path = Some(zipfile.clone());
z.o.out_path = Some(out_path.clone());
let mut ar = match readzip::read_archive(z) {
Ok(ar) => ar,
Err(x) => return x,
};
if ar.zfiles.is_empty() {
return z.ziperr(ZE_NAME, &zipfile);
}
let decrypt = o.decrypt;
if !ar.zfiles.iter().any(|f| (f.e.flg & 1 != 0) == decrypt) {
let h = if decrypt {
m::NO_ENCRYPTED_FILES
} else {
m::ALL_ENCRYPTED_ALREADY
};
return z.ziperr(ZE_NONE, h.as_bytes());
}
let zp = z.host.path(&zipfile);
if let Err(e) = std::fs::File::options().append(true).create(true).open(&zp) {
return z.ziperr_os(ZE_CREAT, &zipfile, &e);
}
let attr = std::fs::metadata(&zp).map_or(0, |md| unix::meta_of(&md).mode);
let dir: Vec<u8> = match &o.tempath {
Some(t) => {
let mut d = t.clone();
if d.last() != Some(&b'/') {
d.push(b'/');
}
d
}
None => match zipfile.iter().rposition(|&b| b == b'/') {
Some(i) => zipfile[..=i].to_vec(),
None => Vec::new(),
},
};
let (tempzip, r) = mkstemp(&z.host, &dir);
let (file, _armed) = match r {
Ok(x) => x,
Err(e) => return z.ziperr_os(ZE_TEMP, &tempzip, &e),
};
let mut temp = Temp {
path: Some(z.host.path(&tempzip)),
};
let Some(passwd) = super::ttyio::getp(z, m::ENTER_PASSWORD.as_bytes()) else {
return z.ziperr(ZE_PARMS, m::CLOAK_STDERR_NOT_TTY.as_bytes());
};
if !decrypt {
let Some(verify) = super::ttyio::getp(z, m::VERIFY_PASSWORD.as_bytes()) else {
return z.ziperr(ZE_PARMS, m::CLOAK_STDERR_NOT_TTY.as_bytes());
};
if passwd.key() != verify.key() {
return z.ziperr(ZE_PARMS, m::PASSWORD_VERIFICATION_FAILED.as_bytes());
}
if passwd.is_empty_answer() {
return z.ziperr(ZE_PARMS, m::ZERO_LENGTH_PASSWORD.as_bytes());
}
}
let pw = passwd.key().to_vec();
let mut ia = InArchive { f: None };
match CFile::open(z, &zipfile) {
Ok(f) => ia.f = Some(f),
Err(_) => return z.ziperr(ZE_NAME, &zipfile),
}
z.current_in_disk = 0;
let budget = match &z.host {
Host::Sandbox(s) => Some(s.budget.clone()),
Host::Real => None,
};
let mut out = ZOut::new(Box::new(FileSink::new(file, budget)), z.o.force_zip64, None);
out.unicode = z.unicode_out();
if ar.zipbeg != 0
&& let Err(f) = readzip::bfcopy(z, &mut ia, &mut out, ar.zipbeg)
{
let h = match &f {
CopyFail::Code(ZE_TEMP, _) => tempzip.clone(),
_ => zipfile.clone(),
};
return fail_exit(z, f, &h);
}
let mut tempzn = ar.zipbeg;
for zf in ar.zfiles.iter_mut() {
if decrypt && zf.e.flg & 1 != 0 {
let mut l = Vec::new();
printf(&mut l, m::DECRYPTING, &[&zf.zname]);
z.stdout_write(&l);
match zipbare(z, &mut ia, &mut out, &mut tempzn, zf, &pw) {
Ok(()) => {}
Err(CopyFail::Code(ZE_MISS, _)) => {
z.stdout_write(m::WRONG_PASSWORD_JUST_COPYING.as_bytes());
}
Err(f) => return fail_exit(z, f, m::WAS_DECRYPTING.as_bytes()),
}
z.stdout_write(b"\n");
} else if !decrypt && zf.e.flg & 1 == 0 {
let mut l = Vec::new();
printf(&mut l, m::ENCRYPTING, &[&zf.zname]);
z.stdout_write(&l);
if let Err(f) = zipcloak(z, &mut ia, &mut out, &mut tempzn, zf, &pw) {
return fail_exit(z, f, m::WAS_ENCRYPTING.as_bytes());
}
} else {
let mut l = Vec::new();
printf(&mut l, m::CLOAK_COPYING, &[&zf.zname]);
z.stdout_write(&l);
if let Err(f) = readzip::zipcopy(z, &mut ia, &mut out, &mut tempzn, zf) {
return fail_exit(z, f, m::WAS_COPYING_AN_ENTRY.as_bytes());
}
}
}
ia.f = None;
let mut central: Vec<ZEntry> = ar.zfiles.iter().map(|f| f.e.clone()).collect();
let mut at = out.w.offset();
let r = zipfile::write_central_as(
&mut central,
&ar.zcomment,
&mut out,
&mut at,
&mut |_, _| {},
true,
);
match r {
Ok(_) => {}
Err(PutError::Big { .. }) => return z.ziperr(ZE_BIG, &tempzip),
Err(PutError::Write(err)) => return write_failed(z, &err),
}
if let Err(err) = out.w.flush() {
return z.ziperr_os(ZE_TEMP, &tempzip, &err);
}
drop(out);
let tmp = temp.path.take().unwrap_or_default();
if let Err((code, err)) = replace(&z.host.path(&out_path), &tmp) {
z.zipwarn(m::NEW_ZIP_LEFT_AS.as_bytes(), &tempzip);
return zerr(z, code, m::WAS_REPLACING.as_bytes(), Some(&err));
}
drop(_armed);
if attr != 0 {
set_mode(&zp, attr);
}
Exit { code: ZE_OK }
}
fn local_entry(z: &mut Zr, ia: &mut InArchive, zf: &ZFile) -> Result<ZEntry, Fail> {
let mut here = zf.clone();
here.e.dsk = z.current_in_disk;
let local = readzip::readlocal(z, ia, &here).map_err(|f| match f {
readzip::LocalFail::Code(c) => CopyFail::Code(c, None),
readzip::LocalFail::Exit(x) => CopyFail::Exit(x),
})?;
let mut iname = local.iname;
if let Some(b) = iname.get_mut(zf.e.iname.len()) {
*b = 0;
}
let lz = ZEntry {
name: readzip::cstr(&iname).to_vec(),
iname,
oname: b"(null)".to_vec(),
vem: zf.e.vem,
ver: local.ver,
flg: local.lflg,
lflg: local.lflg,
how: local.how,
tim: local.tim,
crc: local.crc,
siz: zf.e.siz,
len: zf.e.len,
extra: local.extra,
..ZEntry::default()
};
Ok(lz)
}
fn put(z: &mut Zr, lz: &mut ZEntry, out: &mut ZOut) -> Result<(), Fail> {
out.zip64_entry = get_extra_field(0x0001, &lz.extra).is_some();
match zipfile::putlocal(lz, out) {
Ok(()) => Ok(()),
Err(PutError::Write(err)) => Err(CopyFail::Write(err)),
Err(pe) => Err(CopyFail::Exit(put_failed(z, lz, pe))),
}
}
fn zipcloak(
z: &mut Zr,
ia: &mut InArchive,
out: &mut ZOut,
tempzn: &mut u64,
zf: &mut ZFile,
passwd: &[u8],
) -> Result<(), Fail> {
let n = out.w.offset();
let mut lz = local_entry(z, ia, zf)?;
zf.e.dsk = 0;
zf.e.off = n;
zf.e.flg |= 1;
zf.e.flg &= !8;
lz.lflg |= 1;
lz.lflg &= !8;
lz.siz = lz.siz.wrapping_add(RAND_HEAD_LEN as u64);
zf.e.siz = lz.siz;
put(z, &mut lz, out)?;
let mut keys = Keys::new(passwd);
let Ok(random) = super::ttyio::random_header() else {
return Err(CopyFail::Exit(
z.ziperr(ZE_LOGIC, m::NO_RANDOM_BYTES.as_bytes()),
));
};
let h = crypt_header(&mut keys, random, (lz.crc >> 16) as u16);
out.bfwrite(&h, Bf::Data).map_err(CopyFail::Write)?;
let mut left = lz.siz.wrapping_sub(RAND_HEAD_LEN as u64);
while left > 0 {
let k = left.min(BUF as u64) as usize;
let f = ia.f.as_mut().ok_or(CopyFail::Code(ZE_EOF, None))?;
let mut b = read_exact(f, k)?;
keys.encrypt(&mut b);
out.bfwrite(&b, Bf::Data).map_err(CopyFail::Write)?;
left -= k as u64;
}
*tempzn = tempzn
.wrapping_add(4 + LOCHEAD + lz.iname.len() as u64 + lz.extra.len() as u64)
.wrapping_add(lz.siz);
Ok(())
}
fn zipbare(
z: &mut Zr,
ia: &mut InArchive,
out: &mut ZOut,
tempzn: &mut u64,
zf: &mut ZFile,
passwd: &[u8],
) -> Result<(), Fail> {
let n = out.w.offset();
let mut lz = local_entry(z, ia, zf)?;
zf.e.dsk = 0;
zf.e.off = n;
let mut keys = Keys::new(passwd);
let f = ia.f.as_mut().ok_or(CopyFail::Code(ZE_EOF, None))?;
let head = read_exact(f, RAND_HEAD_LEN)?;
let c1 = head.iter().fold(0u8, |_, &c| keys.decrypt_byte(c));
let check = if zf.e.flg & 8 != 0 {
((zf.e.tim & 0xffff) >> 8) as u8
} else {
(zf.e.crc >> 24) as u8
};
if c1 != check {
if !f.seek(i128::from(n)) {
return Err(CopyFail::Code(
if f.ferror() { ZE_READ } else { ZE_EOF },
None,
));
}
if let Err(e) = readzip::zipcopy(z, ia, out, tempzn, zf) {
return Err(CopyFail::Exit(fail_exit(
z,
e,
m::WAS_COPYING_AN_ENTRY.as_bytes(),
)));
}
return Err(CopyFail::Code(ZE_MISS, None));
}
zf.e.siz = zf.e.siz.wrapping_sub(RAND_HEAD_LEN as u64);
lz.siz = zf.e.siz;
zf.e.flg &= !9;
lz.flg = zf.e.flg;
lz.lflg &= !9;
zf.e.lflg = lz.lflg;
put(z, &mut lz, out)?;
let mut left = zf.e.siz;
while left > 0 {
let k = left.min(BUF as u64) as usize;
let f = ia.f.as_mut().ok_or(CopyFail::Code(ZE_EOF, None))?;
let mut b = read_exact(f, k)?;
keys.decrypt(&mut b);
out.bfwrite(&b, Bf::Data).map_err(CopyFail::Write)?;
left -= k as u64;
}
*tempzn = tempzn
.wrapping_add(4 + LOCHEAD + lz.iname.len() as u64 + lz.extra.len() as u64)
.wrapping_add(lz.siz);
Ok(())
}
#[cfg(test)]
mod tests;