use std::fs::File;
use std::io::{self, Write};
use stuffr::infozip::ByteSource;
use stuffr::infozip::crypt::Keys;
use super::super::fileio::{self, INBUFSIZ, OUTBUFSIZ, ZipIn, c_str};
use super::super::messages as m;
use super::super::process::{self, EB_UT_FL_MTIME, IzTimes};
use super::super::unix::beneath::{self, Dir};
use super::super::unix::mapname::{self, strerror};
use super::super::{PK_OK, Uz, flavour};
use super::{DOES_NOT_EXIST, EXISTS_AND_NEWER, EXISTS_AND_OLDER, Ex, OVERWRT_ALWAYS, PK_DISK};
use crate::compat::host::{self, Host};
pub(crate) struct OutFile {
pub(crate) file: File,
}
const CR: u8 = 13;
const LF: u8 = 10;
const CTRLZ: u8 = 26;
const ANSWERBUF: usize = 10;
fn fgets(uz: &mut Uz, size: usize) -> Option<Vec<u8>> {
let input = uz.stdin();
let mut line = Vec::new();
while line.len() + 1 < size {
let buf = match input.fill_buf() {
Ok(b) => b,
Err(_) => break,
};
if buf.is_empty() {
break;
}
let want = size - 1 - line.len();
let take = match buf.iter().take(want).position(|&b| b == b'\n') {
Some(i) => i + 1,
None => buf.len().min(want),
};
line.extend_from_slice(&buf[..take]);
input.consume(take);
if line.last() == Some(&b'\n') {
break;
}
}
(!line.is_empty()).then_some(line)
}
pub(crate) fn fgets_answer(uz: &mut Uz) -> bool {
match fgets(uz, ANSWERBUF) {
Some(line) => {
uz.answerbuf = [0; ANSWERBUF];
uz.answerbuf[..line.len()].copy_from_slice(&line);
true
}
None => false,
}
}
pub(crate) fn fgets_filename(uz: &mut Uz) -> bool {
let size = uz.filename.len();
match fgets(uz, size) {
Some(line) => {
uz.filename[..line.len()].copy_from_slice(&line);
uz.filename[line.len()] = 0;
true
}
None => false,
}
}
pub(crate) fn check_for_newer(uz: &mut Uz, ex: &Ex, filename: &[u8]) -> i32 {
let p = uz.host.path(filename);
let Ok(st) = std::fs::metadata(&p) else {
if std::fs::symlink_metadata(&p).is_ok() {
if uz.o.qflag == 0 && uz.overwrite_mode != OVERWRT_ALWAYS {
let shown = fileio::fnfilter1(uz, filename);
uz.info_fmt(0, m::FILE_IS_SYMLINK, &[&shown, m::WITH_NO_REAL_FILE]);
}
return EXISTS_AND_OLDER; }
return DOES_NOT_EXIST;
};
if std::fs::symlink_metadata(&p).is_ok_and(|md| md.file_type().is_symlink()) {
if uz.o.qflag == 0 && uz.overwrite_mode != OVERWRT_ALWAYS {
let shown = fileio::fnfilter1(uz, filename);
uz.info_fmt(0, m::FILE_IS_SYMLINK, &[&shown, b""]);
}
return EXISTS_AND_OLDER; }
let st_mtime = mtime_of(&st);
let mut z = IzTimes::default();
let ut = ex.extra_field.as_deref().is_some_and(|ef| {
process::ef_scan_for_izux(
uz.flavour,
ef,
false,
uz.lrec.last_mod_dos_datetime,
Some(&mut z),
) & EB_UT_FL_MTIME
!= 0
});
let (existing, archive) = if ut {
(st_mtime, z.mtime)
} else {
let existing = if st_mtime & 1 != 0 && st_mtime.checked_add(1).is_some() {
st_mtime + 1
} else {
st_mtime
};
(
existing,
fileio::dos_to_unix_time(uz, uz.lrec.last_mod_dos_datetime),
)
};
if existing >= archive {
EXISTS_AND_NEWER
} else {
EXISTS_AND_OLDER
}
}
fn mtime_of(md: &std::fs::Metadata) -> i64 {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
md.mtime()
}
#[cfg(not(unix))]
{
md.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map_or(0, |d| d.as_secs() as i64)
}
}
pub(crate) fn same_as_archive(uz: &Uz, st: &beneath::Stat) -> bool {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
uz.zin
.fd
.as_ref()
.and_then(|f| f.metadata().ok())
.is_some_and(|md| md.dev() == st.dev && md.ino() == st.ino)
}
#[cfg(not(unix))]
{
let _ = (uz, st);
false
}
}
fn archive_busy() -> io::Error {
#[cfg(unix)]
{
io::Error::from_raw_os_error(libc::EBUSY)
}
#[cfg(not(unix))]
{
io::Error::from(io::ErrorKind::ResourceBusy)
}
}
pub(crate) fn open_outfile(uz: &mut Uz, ex: &mut Ex) -> i32 {
let full = c_str(&uz.filename).to_vec();
let apple = flavour::apple_root(uz.flavour);
let parent = mapname::target(uz, &full).and_then(|(root, rel)| root.parent(rel));
let (pd, fname) = match parent {
Ok(p) => p,
Err(e) => {
if apple {
let e = if beneath::is_escape(&e) {
beneath::eacces()
} else {
e
};
let dir = match full.iter().rposition(|&b| b == b'/') {
Some(i) => full[..i].to_vec(),
None => full.clone(),
};
let shown = mapname::shown(uz, &dir);
let err = host::strerror(&e);
uz.info_fmt(0x401, m::CANNOT_ENTER_DIR, &[&shown, &err]);
} else {
cannot_create(uz, &full, &e);
}
return 1;
}
};
if let Ok(st) = pd.lstat(&fname) {
if same_as_archive(uz, &st) {
cannot_create(uz, &full, &archive_busy());
return 1;
}
if uz.o.b_flag {
if backup(uz, &pd, &fname, &full) != 0 {
return 1;
}
} else if let Err(e) = pd.unlink(&fname, false) {
let shown = mapname::shown(uz, &full);
let err = strerror(uz, &e);
uz.info_fmt(0x401, m::CANNOT_DELETE_OLD_FILE, &[&shown, &err]);
return 1;
}
}
let file = pd.create_file(&fname).and_then(|f| {
match super::super::unix::attrs::quarantine_fd(uz, &f) {
Ok(()) => Ok(f),
Err(e) => {
let _ = pd.unlink(&fname, false);
Err(e)
}
}
});
match file {
Ok(file) => {
ex.outfile = Some(OutFile { file });
0
}
Err(e) => {
cannot_create(uz, &full, &e);
1
}
}
}
fn cannot_create(uz: &mut Uz, full: &[u8], e: &io::Error) {
let shown = mapname::shown(uz, full);
let err = strerror(uz, e);
uz.info_fmt(0x401, m::CANNOT_CREATE_FILE, &[&shown, &err]);
}
fn backup(uz: &mut Uz, pd: &Dir, fname: &[u8], full: &[u8]) -> i32 {
let filnamsiz = uz.filename.len();
let apple = flavour::apple_root(uz.flavour);
let base: &[u8] = if apple { fname } else { full };
let blen = m::BACKUP_SUFFIX.len();
let mut flen = base.len();
let mut tlen = flen + blen + 6; let mut tname: Vec<u8>;
if tlen >= filnamsiz {
tlen = filnamsiz - 1 - blen;
tname = base[..base.len().min(tlen)].to_vec();
if flen > tlen {
flen = tlen;
}
tlen = filnamsiz;
} else {
tname = base.to_vec();
}
tname.truncate(flen);
tname.extend_from_slice(m::BACKUP_SUFFIX);
let locate = |uz: &Uz, t: &[u8]| -> io::Result<(Dir, Vec<u8>)> {
if apple {
Ok((pd.reopen()?, t.to_vec()))
} else {
let (root, rel) = mapname::target(uz, t)?;
root.parent(rel)
}
};
let exists = |uz: &Uz, t: &[u8]| locate(uz, t).is_ok_and(|(d, n)| d.lstat(&n).is_ok());
let is_archive = |uz: &Uz, t: &[u8]| {
locate(uz, t).is_ok_and(|(d, n)| d.lstat(&n).is_ok_and(|st| same_as_archive(uz, &st)))
};
if uz.overwrite_mode == OVERWRT_ALWAYS {
if is_archive(uz, &tname) {
cannot_create(uz, full, &archive_busy());
return 1;
}
if let Ok((d, n)) = locate(uz, &tname)
&& d.lstat(&n).is_ok()
{
let _ = d.unlink(&n, false);
}
} else {
let maxtail: u32 = match tlen - flen - blen - 1 {
4 => 9999,
3 => 999,
2 => 99,
1 => 9,
0 => 0,
_ => 99999,
};
let numtail = flen + blen;
let mut i = 0u32;
while i < maxtail && exists(uz, &tname) {
i += 1;
tname.truncate(numtail);
tname.extend_from_slice(i.to_string().as_bytes());
}
}
if is_archive(uz, &tname) {
cannot_create(uz, full, &archive_busy());
return 1;
}
let renamed = locate(uz, &tname).and_then(|(d, n)| pd.rename_to(fname, &d, &n));
if let Err(e) = renamed {
let shown = mapname::shown(uz, full);
let err = strerror(uz, &e);
uz.info_fmt(0x401, m::CANNOT_RENAME_OLD_FILE, &[&shown, &err]);
return 1;
}
0
}
fn write_out(uz: &Uz, ex: &mut Ex, buf: &[u8]) -> io::Result<()> {
uz.host.charge(buf.len() as u64)?;
match ex.outfile.as_mut() {
Some(o) => o.file.write_all(buf),
None => Ok(()),
}
}
fn message_out(uz: &mut Uz, ex: &mut Ex, buf: &[u8]) -> Result<(), i32> {
match uz.print_data(buf) {
Ok(()) => Ok(()),
Err(_) if matches!(uz.host, Host::Sandbox(_)) => Err(disk_error(uz, ex)),
Err(_) => Err(PK_OK),
}
}
fn emit(uz: &mut Uz, ex: &mut Ex, buf: &[u8]) -> Result<(), i32> {
if buf.is_empty() {
return Ok(());
}
if uz.o.cflag {
message_out(uz, ex, buf)
} else if write_out(uz, ex, buf).is_err() {
Err(disk_error(uz, ex))
} else {
Ok(())
}
}
pub(crate) fn flush(uz: &mut Uz, ex: &mut Ex, rawbuf: &[u8], _unshrink: bool) -> i32 {
ex.crc.update(rawbuf);
if uz.o.tflag || rawbuf.is_empty() {
if uz.o.tflag && uz.host.charge(rawbuf.len() as u64).is_err() {
return disk_error(uz, ex);
}
return PK_OK;
}
if ex.disk_full != 0 {
return PK_DISK; }
if !ex.pinfo.textmode {
return match emit(uz, ex, rawbuf) {
Ok(()) => PK_OK,
Err(code) => code,
};
}
if ex.newfile {
ex.vms_line_state = if ex.pinfo.pinfo.hostnum == super::VMS_
&& ex
.extra_field
.as_deref()
.is_some_and(|ef| super::extra::is_vms_varlen_txt(uz, ef))
{
0 } else {
-1 };
ex.did_cr_last = false; ex.newfile = false;
}
let mut q: Vec<u8> = Vec::with_capacity(OUTBUFSIZ.min(rawbuf.len() + 1));
if ex.vms_line_state >= 0 {
if let Err(code) = vms_text(uz, ex, rawbuf, &mut q) {
return code;
}
} else {
let mut p = 0usize;
if rawbuf[0] == LF && ex.did_cr_last {
p += 1;
}
ex.did_cr_last = false;
while p < rawbuf.len() {
let c = rawbuf[p];
if c == CR {
q.push(LF);
if p == rawbuf.len() - 1 {
ex.did_cr_last = true;
} else if rawbuf[p + 1] == LF {
p += 1;
}
} else if c == LF {
q.push(LF);
} else if c != CTRLZ {
q.push(c);
}
p += 1;
}
}
match emit(uz, ex, &q) {
Ok(()) => PK_OK,
Err(code) => code,
}
}
fn vms_text(uz: &mut Uz, ex: &mut Ex, rawbuf: &[u8], q: &mut Vec<u8>) -> Result<(), i32> {
let size = rawbuf.len();
let mut p = 0usize;
while p < size {
match ex.vms_line_state {
0 => {
ex.vms_line_length = 0;
if p == size - 1 {
ex.vms_line_length = u32::from(rawbuf[p]);
p += 1;
ex.vms_line_state = 1;
} else {
ex.vms_line_length = u32::from(fileio::makeword(&rawbuf[p..]));
p += 2;
ex.vms_line_state = 2;
}
ex.vms_line_pad = ex.vms_line_length & 1 != 0; }
1 => {
ex.vms_line_length += u32::from(rawbuf[p]) << 8;
p += 1;
ex.vms_line_state = 2;
}
2 => {
let mut remaining = size - p;
if (ex.vms_line_length as usize) < remaining {
remaining = ex.vms_line_length as usize;
ex.vms_line_state = 3;
}
let outroom = OUTBUFSIZ - q.len();
if remaining >= outroom {
remaining -= outroom;
q.extend_from_slice(&rawbuf[p..p + outroom]);
p += outroom;
emit(uz, ex, q)?;
q.clear();
}
ex.vms_line_length -= remaining as u32;
q.extend_from_slice(&rawbuf[p..p + remaining]);
p += remaining;
}
3 => {
if q.len() > OUTBUFSIZ - 1 {
emit(uz, ex, q)?;
q.clear();
}
q.push(LF);
ex.vms_line_state = if ex.vms_line_pad { 4 } else { 0 };
}
_ => {
p += 1;
ex.vms_line_state = 0;
}
}
}
Ok(())
}
pub(crate) fn disk_error(uz: &mut Uz, ex: &mut Ex) -> i32 {
let name = c_str(&uz.filename).to_vec();
let shown = fileio::fnfilter1(uz, &name);
uz.info_fmt(0x4a1, m::DISK_FULL_QUERY, &[&shown]);
fgets_answer(uz);
ex.disk_full = if uz.answerbuf[0] == b'y' {
1 } else {
2 };
PK_DISK
}
pub(crate) struct EntrySrc {
pub(crate) zin: ZipIn,
pub(crate) csize: i64,
pub(crate) keys: Option<Keys>,
pub(crate) read_error: bool,
}
impl EntrySrc {
fn readbyte(&mut self) -> Option<u8> {
if self.read_error {
return None;
}
if self.csize <= 0 {
self.csize -= 1; self.zin.incnt = 0;
return None;
}
if self.zin.incnt <= 0 {
let n = self.zin.read_at(0, INBUFSIZ);
if n == 0 {
self.zin.incnt = 0;
return None;
} else if n < 0 {
self.read_error = true;
self.zin.incnt = 0;
return None;
}
self.zin.incnt = n;
self.zin.cur_zipfile_bufstart =
self.zin.cur_zipfile_bufstart.wrapping_add(INBUFSIZ as i64);
self.zin.inptr = 0;
defer_leftover_input(&mut self.zin, &mut self.csize); if let Some(k) = self.keys.as_mut() {
let (a, b) = (
self.zin.inptr,
self.zin.inptr + self.zin.incnt.max(0) as usize,
);
let b = b.min(self.zin.inbuf.len());
k.decrypt(&mut self.zin.inbuf[a.min(b)..b]);
}
}
self.zin.incnt -= 1;
let b = self.zin.inbuf.get(self.zin.inptr).copied().unwrap_or(0);
self.zin.inptr += 1;
Some(b)
}
}
impl EntrySrc {
pub(crate) fn fillinbuf(&mut self) -> i64 {
let n = self.zin.read_at(0, INBUFSIZ);
self.zin.incnt = n;
if n <= 0 {
return 0;
}
self.zin.cur_zipfile_bufstart = self.zin.cur_zipfile_bufstart.wrapping_add(INBUFSIZ as i64);
self.zin.inptr = 0;
defer_leftover_input(&mut self.zin, &mut self.csize); if let Some(k) = self.keys.as_mut() {
let b = (self.zin.incnt.max(0) as usize).min(self.zin.inbuf.len());
k.decrypt(&mut self.zin.inbuf[..b]);
}
self.zin.incnt
}
}
impl ByteSource for EntrySrc {
fn next_byte(&mut self) -> Option<u8> {
if self.read_error {
return None;
}
let had = self.zin.incnt;
self.zin.incnt -= 1;
if had > 0 {
let b = self.zin.inbuf.get(self.zin.inptr).copied().unwrap_or(0);
self.zin.inptr += 1;
Some(b)
} else {
self.readbyte()
}
}
fn remaining(&self) -> u64 {
(self.csize.max(0) + self.zin.incnt.max(0)) as u64
}
}
pub(crate) fn defer_leftover_input(zin: &mut ZipIn, csize: &mut i64) {
if zin.incnt > *csize {
if *csize < 0 {
*csize = 0;
}
zin.inptr_leftover = zin.inptr + *csize as usize;
zin.incnt_leftover = zin.incnt - *csize;
zin.incnt = *csize;
} else {
zin.incnt_leftover = 0;
}
*csize -= zin.incnt;
}
pub(crate) fn undefer_input(uz: &mut Uz, zin: &mut ZipIn, csize: &mut i64) {
if flavour::cve_2019_13232_undefer_input(uz.flavour) && *csize < 0 {
*csize = 0;
}
if zin.incnt > 0 {
*csize += zin.incnt;
}
if zin.incnt_leftover > 0 {
zin.incnt = zin.incnt_leftover + *csize;
zin.inptr = (zin.inptr_leftover as i64 - *csize).max(0) as usize;
zin.incnt_leftover = 0;
} else if zin.incnt < 0 {
zin.incnt = 0;
}
}
pub(crate) fn finish_read(uz: &mut Uz, read_error: bool) {
if read_error {
uz.print_raw(0x401, m::READ_ERROR.as_bytes());
uz.abort = Some(super::super::PK_BADERR);
}
}