use std::collections::HashMap;
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::{TcpStream, ToSocketAddrs};
use std::path::Path;
use std::time::Duration;
use std::sync::atomic::Ordering;
use std::os::unix::io::AsRawFd;
#[allow(unused_variables)]
pub fn zftp_session(name: &str, args: &[&str], flags: i32) -> i32 { if args.is_empty() { if let Ok(state) = zftp_state().lock() {
for sess_name in state.session_names() { println!("{}", sess_name); }
}
return 0; }
let current = zftp_state().lock().ok()
.and_then(|s| s.current_name().map(|n| n.to_string()))
.unwrap_or_default();
if args[0] == current {
return 0; }
savesession(); switchsession(args[0]); 0 }
#[allow(non_camel_case_types)]
pub type Zftp_session = Box<zftp_session>;
#[allow(non_camel_case_types)]
pub struct zfheader {
pub flags: i8, pub bytes: [u8; 2], }
#[allow(non_camel_case_types)]
pub struct zftpcmd {
pub nam: &'static str, pub fun: fn(&str, &[&str], i32) -> i32, pub min: i32, pub max: i32,
pub flags: i32,
}
#[allow(non_camel_case_types)]
pub type Zftpcmd = Box<zftpcmd>;
pub static lastcode: std::sync::atomic::AtomicI32
= std::sync::atomic::AtomicI32::new(0);
#[allow(non_snake_case)]
#[inline]
pub fn ZFST_TYPE(x: i32) -> i32 { x & ZFST_TMSK }
#[allow(non_snake_case)]
#[inline]
pub fn ZFST_MODE(x: i32) -> i32 { x & ZFST_MMSK }
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub struct zftp_session {
pub name: String, pub params: Vec<String>, pub userparams: Vec<String>, pub cin: Option<TcpStream>, pub control: Option<TcpStream>, pub dfd: i32, pub has_size: i32, pub has_mdtm: i32,
pub host: Option<String>, pub port: u16, pub user: Option<String>, pub pwd: Option<String>, pub connected: bool, pub logged_in: bool, pub transfer_type: i32,
pub transfer_mode: i32,
pub passive: bool,
pub syst_probed: bool,
}
#[allow(non_upper_case_globals)]
pub static zfprefs: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
#[allow(non_snake_case)]
pub extern "C" fn zfhandler(sig: i32) { if sig == libc::SIGALRM { ZFDRRRRING.store(1, std::sync::atomic::Ordering::Relaxed); unsafe {
*errno_ptr() = libc::ETIMEDOUT;
}
}
}
#[allow(non_snake_case)]
pub fn zfalarm(tmout: i32) { ZFDRRRRING.store(0, std::sync::atomic::Ordering::Relaxed); if ZFALARMED.load(std::sync::atomic::Ordering::Relaxed) != 0 { unsafe { libc::alarm(tmout as u32); } return; }
unsafe {
libc::signal(libc::SIGALRM, zfhandler as libc::sighandler_t);
}
let oalremain = unsafe { libc::alarm(tmout as u32) };
OALREMAIN.store(oalremain, std::sync::atomic::Ordering::Relaxed);
if oalremain != 0 { let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
OALTIME.store(now, std::sync::atomic::Ordering::Relaxed);
}
ZFALARMED.store(1, std::sync::atomic::Ordering::Relaxed); }
#[allow(non_snake_case)]
pub fn zfpipe() { unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_IGN);
}
}
#[allow(non_snake_case)]
pub fn zfunalarm() { let oalremain = OALREMAIN.load(std::sync::atomic::Ordering::Relaxed); if oalremain != 0 { let oaltime = OALTIME.load(std::sync::atomic::Ordering::Relaxed); let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let tdiff = now - oaltime; let secs = if (oalremain as i64) < tdiff { 1 } else { (oalremain as i64 - tdiff) as u32
};
unsafe { libc::alarm(secs); } } else {
unsafe { libc::alarm(0); } }
}
#[allow(non_snake_case)]
pub fn zfunpipe() { unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL); } }
#[allow(non_snake_case)]
pub fn zfmovefd(fd: i32) -> i32 {
fd
}
#[allow(non_snake_case)]
pub fn zfsetparam(name: &str, val: &str, flags: i32) { let _ = flags & ZFPM_INTEGER;
if (flags & ZFPM_IFUNSET) != 0 { if crate::ported::params::paramtab().read().map_or(false, |t| t.contains_key(name)) {
return; }
}
crate::ported::params::setsparam(name, val);
let _ = (flags & ZFPM_READONLY) != 0; }
#[allow(non_snake_case)]
pub fn zfunsetparam(name: &str) { std::env::remove_var(name); }
#[allow(non_snake_case)]
pub fn zfargstring(cmd: &str, args: &[&str]) -> String {
let mut s = cmd.to_string();
for a in args {
s.push(' ');
s.push_str(a);
}
s
}
#[allow(non_snake_case)]
pub fn zfgetline(ln: &mut [u8], lnsize: i32, tmout: i32) -> i32 { let mut ch: i32; let mut added: i32 = 0; let mut pcur: usize = 0; let mut cmdbuf: [u8; 3] = [0; 3];
ZCFINISH.store(0, std::sync::atomic::Ordering::Relaxed); let lnsize = lnsize - 1; if !ln.is_empty() {
ln[0] = 0; }
if ZFDRRRRING.load(std::sync::atomic::Ordering::Relaxed) != 0 { unsafe { libc::alarm(0); } crate::ported::utils::zwarnnam("zftp", "timeout getting response"); return 6; }
zfalarm(tmout);
let mut state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 6,
};
let sess = match state.get_session_mut(None) {
Some(s) => s,
None => return 6,
};
let stream = match sess.cin.as_mut() {
Some(s) => s,
None => return 6,
};
let mut byte = [0u8; 1];
'main: loop { ch = match stream.read(&mut byte) {
Ok(0) => -1, Ok(_) => byte[0] as i32,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, Err(_) => -1,
};
match ch {
-1 => { ZCFINISH.store(2, std::sync::atomic::Ordering::Relaxed); }
0x0d => { ch = match stream.read(&mut byte) { Ok(0) => -1,
Ok(_) => byte[0] as i32,
Err(_) => -1,
};
if ch == -1 { ZCFINISH.store(2, std::sync::atomic::Ordering::Relaxed); } else if ch == 0x0a { ZCFINISH.store(1, std::sync::atomic::Ordering::Relaxed); } else if ch == 0x00 { ch = 0x0d; } else {
ch = 0x0d; }
}
0x0a => { ZCFINISH.store(1, std::sync::atomic::Ordering::Relaxed); }
255 => { ch = match stream.read(&mut byte) { Ok(0) => -1,
Ok(_) => byte[0] as i32,
Err(_) => -1,
};
match ch {
251 | 252 => { ch = match stream.read(&mut byte) { Ok(0) => -1,
Ok(_) => byte[0] as i32,
Err(_) => -1,
};
cmdbuf[0] = 255; cmdbuf[1] = 254; cmdbuf[2] = ch as u8; if let Some(ctrl) = sess.control.as_mut() {
let _ = ctrl.write_all(&cmdbuf);
}
continue 'main; }
253 | 254 => { ch = match stream.read(&mut byte) { Ok(0) => -1,
Ok(_) => byte[0] as i32,
Err(_) => -1,
};
cmdbuf[0] = 255; cmdbuf[1] = 252; cmdbuf[2] = ch as u8; if let Some(ctrl) = sess.control.as_mut() {
let _ = ctrl.write_all(&cmdbuf);
}
continue 'main; }
-1 => { ZCFINISH.store(2, std::sync::atomic::Ordering::Relaxed); }
_ => {} }
}
_ => {}
}
if ZCFINISH.load(std::sync::atomic::Ordering::Relaxed) != 0 {
break;
}
if added < lnsize && pcur < ln.len() {
ln[pcur] = ch as u8;
pcur += 1;
added += 1;
}
}
unsafe { libc::alarm(0); } if pcur < ln.len() {
ln[pcur] = 0; }
ZCFINISH.load(std::sync::atomic::Ordering::Relaxed) & 2
}
#[allow(non_snake_case)]
pub fn zfgetmsg() -> i32 { let mut line = [0u8; 256];
let mut printing: i32 = 0;
let stopit_initial: bool;
let tmout: i32;
{
let state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 6,
};
let sess = match state.get_session(None) {
Some(s) => s,
None => return 6,
};
if sess.control.is_none() {
return 6; }
}
if let Ok(mut m) = lastmsg.lock() {
m.clear();
}
tmout = crate::ported::params::getiparam("ZFTP_TMOUT") as i32;
zfgetline(&mut line, 256, tmout);
let mut ptr_off: usize = 0;
let line_str = std::str::from_utf8(&line)
.unwrap_or("")
.trim_end_matches('\0')
.to_string();
let is_digit = |b: u8| b.is_ascii_digit();
let timeout_or_bad = ZFDRRRRING.load(std::sync::atomic::Ordering::Relaxed) != 0
|| line.len() < 3
|| !is_digit(line[0])
|| !is_digit(line[1])
|| !is_digit(line[2]);
if timeout_or_bad { ZCFINISH.store(2, std::sync::atomic::Ordering::Relaxed); if ZFCLOSING.load(std::sync::atomic::Ordering::Relaxed) == 0 { zfclose(0); }
if let Ok(mut m) = lastmsg.lock() { m.clear(); } if let Ok(mut cs) = lastcodestr.lock() { cs.copy_from_slice(b"000\0");
}
zfsetparam("ZFTP_REPLY", "", ZFPM_READONLY); return 6; }
let code_str: String = std::str::from_utf8(&line[..3]).unwrap_or("0").to_string();
if let Ok(mut cs) = lastcodestr.lock() {
cs[0] = line[0]; cs[1] = line[1]; cs[2] = line[2]; cs[3] = 0;
}
let code: i32 = code_str.parse().unwrap_or(0);
lastcode.store(code, std::sync::atomic::Ordering::Relaxed);
ptr_off += 3;
zfsetparam("ZFTP_CODE", &code_str, ZFPM_READONLY);
stopit_initial = line.get(ptr_off).copied() != Some(b'-');
ptr_off += 1;
let mut stopit = stopit_initial;
let verbose = crate::ported::params::getsparam("ZFTP_VERBOSE").unwrap_or_default(); if verbose.contains(line[0] as char) { printing = 1; eprint!("{}", line_str); } else if verbose.contains('0') && !stopit { printing = 2; eprint!("{}", &line_str[ptr_off..]); }
if printing != 0 { eprintln!(); }
while ZCFINISH.load(std::sync::atomic::Ordering::Relaxed) != 2 && !stopit {
line.fill(0); ptr_off = 0;
zfgetline(&mut line, 256, tmout); if ZFDRRRRING.load(std::sync::atomic::Ordering::Relaxed) != 0 { line[0] = 0; break; }
if &line[..3] == &code_str.as_bytes()[..3] { if line[3] == b' ' { stopit = true; ptr_off = 4; } else if line[3] == b'-' { ptr_off = 4; }
} else if &line[..4] == b" " { ptr_off = 4; }
let cont_line = std::str::from_utf8(&line)
.unwrap_or("")
.trim_end_matches('\0');
if printing == 2 { if !stopit { eprintln!("{}", &cont_line[ptr_off..]); }
} else if printing != 0 { eprintln!("{}", cont_line); }
}
if printing != 0 {
let _ = std::io::stderr().flush();
}
let last_msg_str: String = std::str::from_utf8(&line)
.unwrap_or("")
.trim_end_matches('\0')
.chars()
.skip(ptr_off)
.collect();
if let Ok(mut m) = lastmsg.lock() {
*m = last_msg_str.clone();
}
let whole_line = std::str::from_utf8(&line)
.unwrap_or("")
.trim_end_matches('\0');
zfsetparam("ZFTP_REPLY", whole_line, ZFPM_READONLY);
let zcfin = ZCFINISH.load(std::sync::atomic::Ordering::Relaxed);
let cur_code = lastcode.load(std::sync::atomic::Ordering::Relaxed);
if (zcfin == 2 || cur_code == 421)
&& ZFCLOSING.load(std::sync::atomic::Ordering::Relaxed) == 0 {
ZCFINISH.store(2, std::sync::atomic::Ordering::Relaxed); zfclose(0); crate::ported::utils::zwarnnam("zftp", "remote server has closed connection");
return 6; }
if cur_code == 530 { return 6; }
if cur_code == 120 { crate::ported::utils::zwarnnam("zftp", &format!("delay expected, waiting: {}", last_msg_str));
return zfgetmsg(); }
(code_str.as_bytes()[0] - b'0') as i32
}
#[allow(non_snake_case)]
pub fn zfsendcmd(cmd: &str) -> i32 { let ret: isize;
let tmout: i32;
let mut state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 6,
};
let sess = match state.get_session_mut(None) { Some(s) => s,
None => return 6,
};
if sess.control.is_none() { return 6; }
tmout = crate::ported::params::getiparam("ZFTP_TMOUT") as i32;
zfalarm(tmout);
let bytes = cmd.as_bytes();
ret = match sess.control.as_mut() {
Some(stream) => match stream.write(bytes) {
Ok(n) => {
let _ = stream.flush();
n as isize
}
Err(_) => -1,
},
None => -1,
};
unsafe { libc::alarm(0); }
if ret <= 0 {
crate::ported::utils::zwarnnam( "zftp send",
&format!("failure sending control message: {}",
std::io::Error::last_os_error()));
return 6; }
drop(state);
zfgetmsg()
}
#[allow(non_snake_case)]
pub fn zfopendata(name: &str) -> (i32, bool) { let prefs = zfprefs.load(Ordering::Relaxed); if (prefs & (ZFPF_SNDP | ZFPF_PASV)) == 0 { crate::ported::utils::zwarnnam(name,
"Must set preference S or P to transfer data"); return (1, false); }
let try_pasv: bool = (prefs & ZFPF_PASV) != 0;
if try_pasv {
if zfsendcmd("PASV\r\n") == 6 { return (1, false); }
let code = lastcode.load(Ordering::Relaxed);
if (500..=504).contains(&code) { zfclosedata(); } else {
let last = lastmsg.lock().ok().map(|m| m.clone()).unwrap_or_default();
let (ip, port) = match parse_pasv_response(&last) { Ok(t) => t,
Err(_) => {
crate::ported::utils::zwarnnam(
name, &format!("bad response to PASV: {}", last));
zfclosedata();
return (1, false); }
};
let addr = format!("{}:{}", ip, port);
let stream = match std::net::TcpStream::connect(&addr) { Ok(s) => s,
Err(_) => {
crate::ported::utils::zwarnnam(name, "can't open data socket");
zfclosedata();
return (1, false);
}
};
let dfd_raw = stream.as_raw_fd();
std::mem::forget(stream);
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.dfd = dfd_raw; }
}
return (0, true); }
}
if (zfprefs.load(Ordering::Relaxed) & ZFPF_SNDP) == 0 { crate::ported::utils::zwarnnam(name,
"only sendport mode available for data"); return (1, false); }
let listener = match std::net::TcpListener::bind("0.0.0.0:0") { Ok(l) => l,
Err(_) => {
crate::ported::utils::zwarnnam(name, "can't bind data socket");
return (1, false); }
};
let local = match listener.local_addr() {
Ok(a) => a,
Err(_) => {
crate::ported::utils::zwarnnam(name, "getsockname failed");
return (1, false);
}
};
let ipv4 = match local.ip() {
std::net::IpAddr::V4(v) => v.octets(),
std::net::IpAddr::V6(_) => {
crate::ported::utils::zwarnnam(name, "PORT mode requires IPv4");
return (1, false);
}
};
let port = local.port();
let port_cmd = format!(
"PORT {},{},{},{},{},{}\r\n",
ipv4[0], ipv4[1], ipv4[2], ipv4[3],
port >> 8, port & 0xff,
);
if zfsendcmd(&port_cmd) > 2 { crate::ported::utils::zwarnnam(name, "PORT command failed");
return (1, false); }
let lfd = listener.as_raw_fd();
std::mem::forget(listener);
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.dfd = lfd; }
}
(0, false) }
#[allow(non_snake_case)]
pub fn zfclosedata() { if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
if sess.dfd == -1 { return; }
unsafe { libc::close(sess.dfd); } sess.dfd = -1; }
}
}
#[allow(non_snake_case)]
pub fn zfgetdata(name: &str, rest: &str, cmd: &str, getsize: i32) -> i32 { let is_passive: bool;
let (rc, ip) = zfopendata(name); if rc != 0 { return 1; }
is_passive = ip;
if !rest.is_empty() && zfsendcmd(rest) > 3 {
zfclosedata();
return 1;
}
if zfsendcmd(cmd) > 2 { zfclosedata(); return 1; }
if getsize != 0 || cmd.starts_with("RETR") {
let cur_last = lastmsg.lock().ok().map(|m| m.clone()).unwrap_or_default();
if let Some(byte_idx) = cur_last.find("bytes") { let prefix = &cur_last[..byte_idx];
let trimmed: String = prefix
.chars()
.rev()
.skip_while(|c| !c.is_ascii_digit())
.take_while(|c| c.is_ascii_digit())
.collect::<String>()
.chars()
.rev()
.collect();
if !trimmed.is_empty() && getsize != 0 {
zfsetparam("ZFTP_SIZE", &trimmed, ZFPM_READONLY | ZFPM_INTEGER); }
}
}
let dfd_raw: i32;
if !is_passive { let mut sa: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let mut sl: libc::socklen_t = std::mem::size_of::<libc::sockaddr_storage>() as _;
let listen_fd = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|x| x.dfd))
.unwrap_or(-1);
let newfd = unsafe {
libc::accept(listen_fd, &mut sa as *mut _ as *mut _, &mut sl)
};
if newfd < 0 { crate::ported::utils::zwarnnam(name,
&format!("unable to accept data: {}", std::io::Error::last_os_error()));
zfclosedata(); return 1;
}
unsafe { libc::close(listen_fd); }
dfd_raw = zfmovefd(newfd);
} else { let cur_dfd = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|x| x.dfd))
.unwrap_or(-1);
dfd_raw = zfmovefd(cur_dfd);
}
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.dfd = dfd_raw; }
}
let li = libc::linger { l_onoff: 1, l_linger: 120 };
unsafe {
libc::setsockopt(dfd_raw, libc::SOL_SOCKET, libc::SO_LINGER,
&li as *const _ as *const libc::c_void,
std::mem::size_of::<libc::linger>() as libc::socklen_t);
}
let tos: libc::c_int = 0x08; unsafe {
libc::setsockopt(dfd_raw, libc::IPPROTO_IP, libc::IP_TOS,
&tos as *const _ as *const libc::c_void,
std::mem::size_of::<libc::c_int>() as libc::socklen_t);
}
unsafe { libc::fcntl(dfd_raw, libc::F_SETFD, libc::FD_CLOEXEC); }
0 }
#[allow(non_snake_case)]
pub fn zfstats(fnam: &str, remote: i32, retsize: &mut libc::off_t, retmdtm: &mut Option<String>,
fd: i32) -> i32 {
let mut sz: libc::off_t = -1; let mut mt: Option<String> = None; let ret: i32;
*retsize = -1; *retmdtm = None;
if remote != 0 {
zfsettype(ZFST_IMAG);
let cmd = format!("SIZE {}\r\n", fnam); ret = zfsendcmd(&cmd); if ret == 6 { return 1; }
let code = lastcode.load(std::sync::atomic::Ordering::Relaxed);
if code < 300 { sz = lastmsg.lock().ok()
.map(|m| m.trim().parse::<libc::off_t>().unwrap_or(-1))
.unwrap_or(-1);
} else if (500..=504).contains(&code) { return 2; } else if code == 550 { return 1; }
let cmd = format!("MDTM {}\r\n", fnam); let ret2 = zfsendcmd(&cmd); if ret2 == 6 { return 1; }
let code = lastcode.load(std::sync::atomic::Ordering::Relaxed);
if code < 300 { mt = lastmsg.lock().ok().map(|m| m.clone());
} else if (500..=504).contains(&code) { return 2; } else if code == 550 { return 1; }
} else { let mut statbuf: libc::stat = unsafe { std::mem::zeroed() }; let cn = std::ffi::CString::new(fnam).unwrap_or_default();
let rc = if fd == -1 { unsafe { libc::stat(cn.as_ptr(), &mut statbuf) }
} else {
unsafe { libc::fstat(fd, &mut statbuf) }
};
if rc < 0 { return 1; }
sz = statbuf.st_size as libc::off_t;
let mtime = statbuf.st_mtime;
let mut tmbuf = [0u8; 20];
let tmbuf_len = unsafe {
let mut tm: libc::tm = std::mem::zeroed();
libc::gmtime_r(&mtime, &mut tm); let fmt = std::ffi::CString::new("%Y%m%d%H%M%S").unwrap();
libc::strftime(
tmbuf.as_mut_ptr() as *mut libc::c_char,
20,
fmt.as_ptr(),
&tm,
)
};
mt = std::str::from_utf8(&tmbuf[..tmbuf_len]).ok().map(|s| s.to_string());
}
*retsize = sz; *retmdtm = mt; 0 }
#[allow(non_snake_case)]
pub fn zfstarttrans(nam: &str, recv: i32, sz: libc::off_t) { let cnt: libc::off_t = 0; if sz > 0 { zfsetparam("ZFTP_SIZE", &sz.to_string(), ZFPM_READONLY | ZFPM_INTEGER); }
zfsetparam("ZFTP_FILE", nam, ZFPM_READONLY); zfsetparam("ZFTP_TRANSFER", if recv != 0 { "G" } else { "P" }, ZFPM_READONLY);
zfsetparam("ZFTP_COUNT", &cnt.to_string(), ZFPM_READONLY | ZFPM_INTEGER); }
#[allow(non_snake_case)]
pub fn zfendtrans() { zfunsetparam("ZFTP_SIZE"); zfunsetparam("ZFTP_FILE"); zfunsetparam("ZFTP_TRANSFER"); zfunsetparam("ZFTP_COUNT"); }
#[allow(non_snake_case)]
pub fn zfread(fd: i32, bf: &mut [u8], sz: libc::off_t, tmout: i32) -> i32 { let ret: isize;
if tmout == 0 {
let n = unsafe {
libc::read(fd, bf.as_mut_ptr() as *mut libc::c_void, sz as libc::size_t)
};
return n as i32; }
if ZFDRRRRING.load(std::sync::atomic::Ordering::Relaxed) != 0 { unsafe { libc::alarm(0); } crate::ported::utils::zwarnnam("zftp", "timeout on network read"); return -1; }
zfalarm(tmout);
ret = unsafe {
libc::read(fd, bf.as_mut_ptr() as *mut libc::c_void, sz as libc::size_t)
};
unsafe { libc::alarm(0); }
ret as i32 }
#[allow(non_snake_case)]
pub fn zfwrite(fd: i32, bf: &[u8], sz: i64, tmout: i32) -> i32 { if tmout == 0 { return unsafe {
libc::write(fd, bf.as_ptr() as *const _, sz as usize) as i32 };
}
zfalarm(tmout); let ret = unsafe {
libc::write(fd, bf.as_ptr() as *const _, sz as usize) as i32 };
unsafe { libc::alarm(0); } ret }
pub static zfread_eof: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
#[allow(non_snake_case)]
pub fn zfread_block(fd: i32, bf: &mut [u8], sz: libc::off_t, tmout: i32) -> i32 { let mut n: i32; let mut hdr = zfheader { flags: 0, bytes: [0u8; 2] }; let mut blksz: libc::off_t = 0; let mut cnt: libc::off_t; let mut bfptr: usize;
loop { let mut hdr_buf = [0u8; 3];
loop { n = zfread(fd, &mut hdr_buf, 3, tmout); if !(n < 0 && std::io::Error::last_os_error().raw_os_error() == Some(libc::EINTR)) {
break;
}
}
if n != 3 && ZFDRRRRING.load(Ordering::Relaxed) == 0 {
crate::ported::utils::zwarnnam("zftp", "failure reading FTP block header");
return n; }
hdr.flags = hdr_buf[0] as i8;
hdr.bytes[0] = hdr_buf[1];
hdr.bytes[1] = hdr_buf[2];
if (hdr.flags as i32 & ZFHD_EOFB) != 0 {
zfread_eof.store(1, Ordering::Relaxed); }
blksz = ((hdr.bytes[0] as libc::off_t) << 8) | (hdr.bytes[1] as libc::off_t);
if blksz > sz {
crate::ported::utils::zwarnnam("zftp", "block too large to handle");
unsafe { *errno_ptr() = libc::EIO; } return -1; }
bfptr = 0; cnt = blksz; while cnt > 0 { let want = cnt as usize;
let end = bfptr + want;
if end > bf.len() { return -1; }
n = zfread(fd, &mut bf[bfptr..end], cnt, tmout); if n > 0 { bfptr += n as usize; cnt -= n as libc::off_t; } else if n < 0 && (
crate::ported::utils::errflag.load(Ordering::Relaxed) != 0
|| ZFDRRRRING.load(Ordering::Relaxed) != 0
|| std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR)
) { return n; } else {
break; }
}
if cnt != 0 {
crate::ported::utils::zwarnnam("zftp", "short data block");
unsafe { *errno_ptr() = libc::EIO; } return -1; }
if !((hdr.flags as i32 & ZFHD_MARK) != 0
&& zfread_eof.load(Ordering::Relaxed) == 0) {
break;
}
}
if (hdr.flags as i32 & ZFHD_MARK) != 0 { 0 } else { blksz as i32 }
}
#[allow(non_snake_case)]
pub fn zfwrite_block(fd: i32, bf: &[u8], sz: i64, tmout: i32) -> i32 { let mut hdr = zfheader { bytes: [0u8; 2], flags: 0i8 }; let mut n: i32;
loop {
hdr.bytes[0] = ((sz & 0xff00) >> 8) as u8; hdr.bytes[1] = (sz & 0xff) as u8; hdr.flags = if sz != 0 { 0i8 } else { ZFHD_EOFB as i8 }; let hdr_bytes = unsafe {
std::slice::from_raw_parts(&hdr as *const _ as *const u8, 3)
};
n = zfwrite(fd, hdr_bytes, 3, tmout); if !(n < 0 && unsafe { *errno_ptr() } == libc::EINTR) { break; } }
if n != 3 { return n; }
if sz != 0 { n = zfwrite(fd, bf, sz, tmout); }
n }
#[allow(non_snake_case)]
pub fn zfsenddata(name: &str, recv: i32, progress: i32, startat: libc::off_t) -> i32 { const ZF_BUFSIZE: usize = 32768;
const ZF_ASCSIZE: usize = ZF_BUFSIZE / 2;
let mut n: i32; let mut ret: i32 = 0; let gotack: i32 = 0; let fdin: i32; let fdout: i32; let mut fromasc: i32 = 0; let mut toasc: i32 = 0; let mut rtmout: i32 = 0; let mut wtmout: i32 = 0; let mut lsbuf = vec![0u8; ZF_BUFSIZE]; let mut ascbuf: Vec<u8> = Vec::new(); let mut sofar: libc::off_t = 0; let mut last_sofar: libc::off_t = 0; let _ = progress;
let mut use_block_mode = false;
{
let state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 1,
};
let sess = match state.get_session(None) {
Some(s) => s,
None => return 1,
};
if recv != 0 { fdin = sess.dfd; fdout = 1; rtmout = crate::ported::params::getiparam("ZFTP_TMOUT") as i32;
if sess.transfer_type == ZFST_ASCI as i32 { fromasc = 1; }
if sess.transfer_mode == ZFST_BLOC as i32 { use_block_mode = true; }
} else { fdin = 0; fdout = sess.dfd; wtmout = crate::ported::params::getiparam("ZFTP_TMOUT") as i32;
if sess.transfer_type == ZFST_ASCI as i32 { toasc = 1; }
if sess.transfer_mode == ZFST_BLOC as i32 { use_block_mode = true; }
}
}
if progress != 0 {
sofar = startat; last_sofar = sofar;
}
let _ = last_sofar;
if toasc != 0 {
ascbuf = vec![0u8; ZF_ASCSIZE]; }
zfpipe(); zfread_eof.store(0, Ordering::Relaxed);
while ret == 0 && zfread_eof.load(Ordering::Relaxed) == 0 {
n = if toasc != 0 {
if use_block_mode {
zfread_block(fdin, &mut ascbuf, ZF_ASCSIZE as libc::off_t, rtmout)
} else {
zfread(fdin, &mut ascbuf, ZF_ASCSIZE as libc::off_t, rtmout)
}
} else if use_block_mode {
zfread_block(fdin, &mut lsbuf, ZF_BUFSIZE as libc::off_t, rtmout)
} else {
zfread(fdin, &mut lsbuf, ZF_BUFSIZE as libc::off_t, rtmout)
};
if n > 0 { if toasc != 0 {
let mut iptr = 0usize;
let mut optr = 0usize;
let mut cnt = n;
while cnt > 0 {
if ascbuf[iptr] == b'\n' { if optr < lsbuf.len() { lsbuf[optr] = b'\r'; optr += 1; }
n += 1; }
if optr < lsbuf.len() { lsbuf[optr] = ascbuf[iptr]; optr += 1; }
iptr += 1;
cnt -= 1;
}
}
if fromasc != 0 {
if let Some(_start) = lsbuf[..n as usize].iter().position(|&b| b == b'\r') {
let mut optr = 0usize;
let mut iptr = 0usize;
let len = n as usize;
while iptr < len {
if lsbuf[iptr] != b'\r' || iptr + 1 >= len || lsbuf[iptr + 1] != b'\n' {
lsbuf[optr] = lsbuf[iptr];
optr += 1;
} else {
n -= 1; }
iptr += 1;
}
}
}
let mut optr_off: usize = 0;
sofar += n as libc::off_t; loop { let chunk = &lsbuf[optr_off..optr_off + n as usize];
let newn: i32 = if use_block_mode && recv == 0 {
zfwrite_block(fdout, chunk, n as libc::off_t, wtmout)
} else {
zfwrite(fdout, chunk, n as libc::off_t, wtmout)
};
if newn == n { break; } if newn < 0 { let errno = std::io::Error::last_os_error().raw_os_error();
let drrr = ZFDRRRRING.load(Ordering::Relaxed) != 0;
let efl = crate::ported::utils::errflag.load(Ordering::Relaxed) != 0;
if errno != Some(libc::EINTR) || efl || drrr { if !drrr && (efl || errno != Some(libc::EPIPE)) { ret = if recv != 0 { 2 } else { 1 };
crate::ported::utils::zwarnnam(name, &format!("write failed: {}",
std::io::Error::last_os_error()));
} else {
ret = if recv != 0 { 3 } else { 1 };
}
break;
}
continue; }
optr_off += newn as usize; n -= newn; }
} else if n < 0 { let errno = std::io::Error::last_os_error().raw_os_error();
let drrr = ZFDRRRRING.load(Ordering::Relaxed) != 0;
let efl = crate::ported::utils::errflag.load(Ordering::Relaxed) != 0;
if errno != Some(libc::EINTR) || efl || drrr { if !drrr && (efl || errno != Some(libc::EPIPE)) { ret = if recv != 0 { 1 } else { 2 };
crate::ported::utils::zwarnnam(name, &format!("read failed: {}",
std::io::Error::last_os_error()));
} else {
ret = if recv != 0 { 1 } else { 3 };
}
break;
}
} else { break; }
if ret == 0 && sofar != last_sofar && progress != 0 {
if let Some(_shfunc) = crate::ported::utils::getshfunc("zftp_progress") { let osc = crate::ported::builtin::SFCONTEXT.load(Ordering::Relaxed); zfsetparam("ZFTP_COUNT", &sofar.to_string(),
ZFPM_READONLY | ZFPM_INTEGER); crate::ported::builtin::SFCONTEXT.store(
crate::ported::zsh_h::SFC_HOOK, Ordering::Relaxed); let _ = crate::ported::builtin::LASTVAL.load(Ordering::Relaxed);
crate::ported::builtin::SFCONTEXT.store(osc, Ordering::Relaxed); } else {
zfsetparam("ZFTP_COUNT", &sofar.to_string(),
ZFPM_READONLY | ZFPM_INTEGER); }
last_sofar = sofar; }
}
zfunpipe(); ZFDRRRRING.store(0, Ordering::Relaxed);
if crate::ported::utils::errflag.load(Ordering::Relaxed) == 0
&& ret == 0 && recv == 0 && use_block_mode {
let eof_buf = [0u8; 1];
if zfwrite_block(fdout, &eof_buf, 0, wtmout) < 0 {
ret = 1; }
}
if crate::ported::utils::errflag.load(Ordering::Relaxed) != 0 || ret > 1 {
let msg: [u8; 4] = [255, 244, 255, 242]; if ret == 2 { crate::ported::utils::zwarnnam(name, "aborting data transfer..."); }
crate::ported::signals::holdintr(); if let Ok(state) = zftp_state().lock() {
if let Some(sess) = state.get_session(None) {
if let Some(ref ctrl) = sess.control {
let cfd = ctrl.as_raw_fd();
unsafe {
libc::send(cfd, msg.as_ptr() as *const libc::c_void, 3, 0); libc::send(cfd, msg[3..].as_ptr() as *const libc::c_void, 1, libc::MSG_OOB); }
}
}
}
zfsendcmd("ABOR\r\n"); if lastcode.load(Ordering::Relaxed) != 226 { ret = 1; }
crate::ported::signals::noholdintr(); }
drop(ascbuf);
zfclosedata(); if gotack == 0 && zfgetmsg() > 2 { ret = 1; }
if ret != 0 { 1 } else { 0 } }
pub const ZF_BUFSIZE: usize = 32_768;
pub const ZF_ASCSIZE: usize = ZF_BUFSIZE / 2;
pub fn zftp_open(name: &str, args: &[&str], flags: i32) -> i32 { let mut port: i32 = -1; let portnam: String; let hostnam: String; let hostsuffix: String; let tmout: i32;
let mut effective: Vec<String> = args.iter().map(|s| s.to_string()).collect();
if effective.is_empty() { let up = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.userparams.clone()))
.unwrap_or_default();
if !up.is_empty() {
effective = up; } else {
crate::ported::utils::zwarnnam(name, "no host specified"); return 1; }
}
let already_open = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.control.is_some()))
.unwrap_or(false);
if already_open { zfclose(0); }
let raw = effective[0].clone();
if let Some(stripped) = raw.strip_prefix('[') {
let close_idx = match stripped.find(']') {
Some(i) => i,
None => {
crate::ported::utils::zwarnnam(
name, &format!("Invalid host format: {}", raw)); return 1; }
};
let after = &stripped[close_idx + 1..];
if !after.is_empty() && !after.starts_with(':') { crate::ported::utils::zwarnnam(
name, &format!("Invalid host format: {}", raw));
return 1;
}
hostnam = stripped[..close_idx].to_string(); hostsuffix = after.to_string(); } else {
hostnam = raw.clone();
hostsuffix = raw; }
if let Some((_pre, post)) = hostsuffix.split_once(':') { let trimmed = post.trim();
match trimmed.parse::<i32>() { Ok(p) => { port = p;
portnam = "ftp".to_string();
}
Err(_) => { portnam = trimmed.to_string(); port = -1; }
}
} else {
portnam = "ftp".to_string();
}
let resolved_port: u16 = if port > 0 {
port as u16
} else {
match portnam.as_str() {
"ftp" => 21,
"ftps" => 990,
_ => {
crate::ported::utils::zwarnnam(
name, &format!("Can't find port for service `{}'", portnam)); return 1; }
}
};
ZCFINISH.store(2, Ordering::Relaxed);
tmout = {
let v = crate::ported::params::getiparam("ZFTP_TMOUT") as i32;
if v > 0 { v } else { 60 }
};
zfalarm(tmout);
let target = format!("{}:{}", hostnam, resolved_port);
let addrs: Vec<std::net::SocketAddr> = match target.to_socket_addrs() {
Ok(it) => it.collect(),
Err(_) => {
unsafe { libc::alarm(0); }
crate::ported::utils::zwarnnam(
name, &format!("host not found: {}", hostnam)); return 1; }
};
if addrs.is_empty() {
unsafe { libc::alarm(0); }
crate::ported::utils::zwarnnam(
name, &format!("host not found: {}", hostnam));
return 1;
}
zfsetparam("ZFTP_HOST", &hostnam, ZFPM_READONLY); zfsetparam("ZFTP_PORT", &resolved_port.to_string(),
ZFPM_READONLY | ZFPM_INTEGER);
let mut last_err: Option<std::io::Error> = None;
let mut connected: Option<(TcpStream, std::net::SocketAddr)> = None;
for addr in addrs.iter() { if crate::ported::utils::errflag.load(Ordering::Relaxed) != 0 {
break;
}
match TcpStream::connect_timeout(addr, std::time::Duration::from_secs(tmout.max(1) as u64)) {
Ok(s) => { connected = Some((s, *addr)); break; } Err(e) => { last_err = Some(e); } }
}
let (stream, used_addr) = match connected {
Some(v) => v,
None => {
unsafe { libc::alarm(0); }
zfunsetparam("ZFTP_HOST"); zfunsetparam("ZFTP_PORT"); let msg = last_err.as_ref()
.map(|e| e.to_string())
.unwrap_or_else(|| "connect failed".to_string());
crate::ported::utils::zwarnnam(name, &format!("connect failed: {}", msg)); return 1; }
};
let pbuf = used_addr.ip().to_string();
zfsetparam("ZFTP_IP", &pbuf, ZFPM_READONLY);
unsafe { libc::alarm(0); }
ZFNOPEN.fetch_add(1, Ordering::Relaxed);
ZCFINISH.store(0, Ordering::Relaxed);
let fd = stream.as_raw_fd();
unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC); }
unsafe {
let one: libc::c_int = 1;
libc::setsockopt(fd, libc::SOL_SOCKET, libc::SO_OOBINLINE,
&one as *const _ as *const _,
std::mem::size_of::<libc::c_int>() as libc::socklen_t);
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
unsafe {
let lowdelay: libc::c_int = 0x10; libc::setsockopt(fd, libc::IPPROTO_IP, libc::IP_TOS,
&lowdelay as *const _ as *const _,
std::mem::size_of::<libc::c_int>() as libc::socklen_t);
}
let stream_clone = match stream.try_clone() {
Ok(c) => c,
Err(_) => {
crate::ported::utils::zwarnnam(name, "file handling error"); zfclose(0);
return 1;
}
};
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.control = Some(stream);
sess.cin = Some(stream_clone);
sess.connected = true;
sess.host = Some(hostnam.clone());
sess.port = resolved_port;
}
}
if zfgetmsg() >= 4 { zfclose(0); return 1; }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.has_size = ZFCP_UNKN; sess.has_mdtm = ZFCP_UNKN; sess.dfd = -1; sess.transfer_type = ZFST_ASCI; }
}
zfsetparam("ZFTP_MODE", "S", ZFPM_READONLY); if effective.len() > 1 { let rest: Vec<&str> = effective[1..].iter().map(|s| s.as_str()).collect();
return zftp_login(name, &rest, flags); }
let alive = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.control.is_some()))
.unwrap_or(false);
if alive { 0 } else { 1 } }
#[allow(non_snake_case)]
pub fn zfgetinfo(prompt: &str, noecho: i32) -> Option<String> { let mut resettty: i32 = 0; let mut instr = String::new(); let len: usize = 0; let _ = len;
let saved_termios: Option<libc::termios>;
if unsafe { libc::isatty(0) } != 0 { if noecho != 0 { let mut ti: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(0, &mut ti) } == 0 {
saved_termios = Some(ti);
ti.c_lflag &= !libc::ECHO; unsafe { libc::tcsetattr(0, libc::TCSANOW, &ti); } resettty = 1; } else {
saved_termios = None;
}
} else {
saved_termios = None;
}
eprint!("{}", prompt); let _ = std::io::stderr().flush(); } else {
saved_termios = None;
}
let stdin = std::io::stdin();
let mut handle = stdin.lock();
match handle.read_line(&mut instr) { Ok(0) => instr.clear(), Ok(_) => { if instr.ends_with('\n') {
instr.pop();
}
}
Err(_) => instr.clear(),
}
let strret = instr.clone();
if resettty != 0 { println!(); let _ = std::io::stdout().flush(); if let Some(ti) = saved_termios { unsafe { libc::tcsetattr(0, libc::TCSANOW, &ti); }
}
}
Some(strret) }
#[allow(unused_variables)]
pub fn zftp_params(name: &str, args: &[&str], flags: i32) -> i32 { let prompts: [&str; 4] = ["Host: ", "User: ", "Password: ", "Account: "]; if args.is_empty() { let state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 1,
};
let sess = match state.get_session(None) {
Some(s) => s,
None => return 1,
};
if sess.userparams.is_empty() { return 1; }
let mut out = std::io::stdout().lock();
for (i, p) in sess.userparams.iter().enumerate() { if i == 2 { let len = p.len(); for _ in 0..len { let _ = out.write_all(b"*"); }
let _ = out.write_all(b"\n"); } else {
let _ = writeln!(out, "{}", p); }
}
return 0; }
if args[0] == "-" { if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.userparams.clear(); }
}
return 0; }
let len = args.len(); let mut newarr: Vec<String> = Vec::with_capacity(len); let efl_atomic = &crate::ported::utils::errflag;
for (i, aptr) in args.iter().enumerate() { if efl_atomic.load(Ordering::Relaxed) != 0 { break;
}
let str_val: String;
if let Some(rest) = aptr.strip_prefix('?') { let prompt: &str = if !rest.is_empty() { rest } else { prompts[i.min(3)] }; match zfgetinfo(prompt, if i == 2 { 1 } else { 0 }) { Some(s) => str_val = s,
None => { return 1; }
}
} else if let Some(rest) = aptr.strip_prefix('\\') { str_val = rest.to_string();
} else {
str_val = (*aptr).to_string();
}
newarr.push(str_val); }
if efl_atomic.load(Ordering::Relaxed) != 0 { return 1; }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.userparams = newarr; }
}
0 }
#[allow(unused_variables)]
pub fn zftp_login(name: &str, args: &[&str], flags: i32) -> i32 { let mut ucmd: String; let mut passwd: Option<String> = None; let mut acct: Option<String> = None; let user: String; let mut stopit: i32; let mut arg_idx: usize = 0;
let already_logged_in = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.logged_in))
.unwrap_or(false);
if already_logged_in && zfsendcmd("REIN\r\n") >= 4 { return 1; }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.logged_in = false; }
}
if arg_idx < args.len() { user = args[arg_idx].to_string(); arg_idx += 1;
} else {
user = match zfgetinfo("User: ", 0) { Some(s) => s,
None => return 1,
};
}
ucmd = format!("USER {}\r\n", user);
stopit = 0;
if zfsendcmd(&ucmd) == 6 { stopit = 2; }
let efl_atomic = &crate::ported::utils::errflag;
while stopit == 0 && efl_atomic.load(Ordering::Relaxed) == 0 { let code = lastcode.load(Ordering::Relaxed);
match code {
230 | 202 => { stopit = 1; }
331 => { let pw = if arg_idx < args.len() { let p = args[arg_idx].to_string(); arg_idx += 1;
p
} else {
match zfgetinfo("Password: ", 1) { Some(s) => s,
None => { stopit = 2; break; }
}
};
passwd = Some(pw.clone()); ucmd = format!("PASS {}\r\n", pw);
if zfsendcmd(&ucmd) == 6 { stopit = 2; }
}
332 | 532 => { let ac = if arg_idx < args.len() { let a = args[arg_idx].to_string(); arg_idx += 1;
a
} else {
match zfgetinfo("Account: ", 0) { Some(s) => s,
None => { stopit = 2; break; }
}
};
acct = Some(ac.clone());
ucmd = format!("ACCT {}\r\n", ac); if zfsendcmd(&ucmd) == 6 { stopit = 2; }
}
_ => {
stopit = 2; }
}
}
let _ = passwd;
let control_alive = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.control.is_some()))
.unwrap_or(false);
if !control_alive { return 1; }
let code = lastcode.load(Ordering::Relaxed);
if stopit == 2 || (code != 230 && code != 202) { crate::ported::utils::zwarnnam(name, "login failed"); return 1; }
if arg_idx < args.len() { let cnt = args.len() - arg_idx; crate::ported::utils::zwarnnam(
name,
&format!("warning: {} command arguments not used", cnt), );
}
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.logged_in = true; sess.user = Some(user.clone());
}
}
zfsetparam("ZFTP_USER", &user, ZFPM_READONLY); if let Some(ref a) = acct { zfsetparam("ZFTP_ACCOUNT", a, ZFPM_READONLY); }
let already_probed = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.syst_probed))
.unwrap_or(false);
let dumb = (zfprefs.load(std::sync::atomic::Ordering::Relaxed)
& ZFPF_DUMB) != 0; if !dumb && !already_probed && zfsendcmd("SYST\r\n") == 2 { let systype = lastmsg.lock().ok().map(|m| m.clone()).unwrap_or_default();
if systype.starts_with("UNIX Type: L8") { if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.transfer_type = ZFST_IMAG; }
}
}
zfsetparam("ZFTP_SYSTEM", &systype, ZFPM_READONLY); if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.syst_probed = true; }
}
}
let ttype = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.transfer_type))
.unwrap_or(ZFST_ASCI);
let tbuf = if ZFST_TYPE(ttype) == ZFST_ASCI { "A" } else { "I" }; zfsetparam("ZFTP_TYPE", tbuf, ZFPM_READONLY);
zfgetcwd() }
#[allow(unused_variables)]
pub fn zftp_test(name: &str, args: &[&str], flags: i32) -> i32 { let control_fd = zftp_state().lock().ok().and_then(|s| {
s.get_session(None).and_then(|sess| {
sess.control.as_ref().map(|c| {
c.as_raw_fd()
})
})
});
let fd = match control_fd { Some(f) => f,
None => return 1, };
let mut pfd = libc::pollfd { fd, events: libc::POLLIN, revents: 0 };
let ret = unsafe { libc::poll(&mut pfd, 1, 0) }; let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if ret < 0 && errno != libc::EINTR && errno != libc::EAGAIN { zfclose(0); } else if ret > 0 && pfd.revents != 0 { zfgetmsg(); }
let still_alive = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.control.is_some()))
.unwrap_or(false);
if still_alive { 0 } else { 2 } }
pub fn zftp_dir(name: &str, args: &[&str], flags: i32) -> i32 { let cmd: String; let ret: i32; zfsettype(ZFST_ASCI);
let verb = if (flags & ZFTP_NLST) != 0 { "NLST" } else { "LIST" };
cmd = zfargstring(verb, args) + "\r\n";
ret = zfgetdata(name, "", &cmd, 0);
if ret != 0 { return 1; } let _ = std::io::stdout().flush(); zfsenddata(name, 1, 0, 0) }
#[allow(unused_variables)]
pub fn zftp_cd(name: &str, args: &[&str], flags: i32) -> i32 { let ret: i32; let arg0 = args.first().copied().unwrap_or("");
if (flags & ZFTP_CDUP) != 0 || arg0 == ".." || arg0 == "../" { ret = zfsendcmd("CDUP\r\n"); } else { let cmd = format!("CWD {}\r\n", arg0); ret = zfsendcmd(&cmd); }
if ret > 2 { return 1; } if zfgetcwd() != 0 { return 1; }
0 }
#[allow(non_snake_case)]
pub fn zfgetcwd() -> i32 {
if (zfprefs.load(std::sync::atomic::Ordering::Relaxed) & ZFPF_DUMB) != 0 { return 1; }
if zfsendcmd("PWD\r\n") > 2 { zfunsetparam("ZFTP_PWD"); return 1; }
if lastcode.load(std::sync::atomic::Ordering::Relaxed) >= 200 { 0 } else { 1 }
}
#[allow(non_snake_case)]
pub fn zfsettype(typ: i32) -> i32 {
let typ_letter = if (typ & ZFST_IMAG) != 0 { "I" } else { "A" };
let _ = zfsendcmd(&format!("TYPE {}\r\n", typ_letter));
zfgetmsg()
}
pub fn zftp_type(name: &str, args: &[&str], flags: i32) -> i32 { let mut tbuf: [u8; 2] = [b'A', 0]; let nt: u8; let str: &str; let _ = str;
if (flags & (ZFTP_TBIN | ZFTP_TASC)) != 0 { nt = if (flags & ZFTP_TBIN) != 0 { b'I' } else { b'A' }; } else if args.first().copied().unwrap_or("").is_empty() { let ttype = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.transfer_type))
.unwrap_or(ZFST_IMAG as i32);
let is_ascii = (ttype & ZFST_ASCI) != 0;
println!("{}", if is_ascii { 'A' } else { 'I' }); let _ = std::io::stdout().flush(); return 0; } else {
str = args[0];
let c0 = str.as_bytes()[0].to_ascii_uppercase(); if str.len() > 1 || (c0 != b'A' && c0 != b'B' && c0 != b'I') { crate::ported::utils::zwarnnam(name, &format!("transfer type {} not recognised", str));
return 1; }
nt = if c0 == b'B' { b'I' } else { c0 }; }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.transfer_type = if nt == b'I' { ZFST_IMAG } else { ZFST_ASCI } as i32;
}
}
tbuf[0] = nt; let tb = std::str::from_utf8(&tbuf[..1]).unwrap_or("A");
zfsetparam("ZFTP_TYPE", tb, ZFPM_READONLY); 0 }
#[allow(unused_variables)]
pub fn zftp_mode(name: &str, args: &[&str], flags: i32) -> i32 { let str: &str;
let nt: u8;
if args.first().copied().unwrap_or("").is_empty() { let tmode = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.transfer_mode))
.unwrap_or(ZFST_STRE as i32);
let is_stream = tmode == ZFST_STRE;
println!("{}", if is_stream { 'S' } else { 'B' }); let _ = std::io::stdout().flush(); return 0; }
str = args[0];
nt = str.as_bytes()[0].to_ascii_uppercase(); if str.len() > 1 || (nt != b'S' && nt != b'B') { crate::ported::utils::zwarnnam(name, &format!("transfer mode {} not recognised", str));
return 1; }
let cmd = format!("MODE {}\r\n", nt as char); if zfsendcmd(&cmd) > 2 { return 1; }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.transfer_mode = if nt == b'S' { ZFST_STRE } else { ZFST_BLOC } as i32;
}
}
let mb = (nt as char).to_string();
zfsetparam("ZFTP_MODE", &mb, ZFPM_READONLY); 0 }
#[allow(unused_variables)]
pub fn zftp_local(name: &str, args: &[&str], flags: i32) -> i32 { let more = args.len() > 1; let mut ret: i32 = 0; let dofd = args.is_empty(); let mut i = 0usize;
loop { if !dofd && i >= args.len() { break; }
let arg = if dofd { "" } else { args[i] };
let mut sz: libc::off_t = 0; let mut mt: Option<String> = None; let remote = if (flags & ZFTP_HERE) != 0 { 0 } else { 1 };
let fd = if dofd { 0 } else { -1 };
let newret = zfstats(arg, remote, &mut sz, &mut mt, fd);
if newret == 2 { return 2; } else if newret != 0 { ret = 1; i += 1; continue; }
if more { print!("{} ", arg); }
let mt_s = mt.unwrap_or_default();
println!("{} {}", sz, mt_s); if dofd { break; } i += 1; }
let _ = std::io::stdout().flush(); ret }
pub fn zftp_getput(name: &str, args: &[&str], flags: i32) -> i32 { let mut ret: i32 = 0; let recv = (flags & ZFTP_RECV) != 0; let mut getsize: i32 = 0; let progress: i32 = 1; let cmd_pfx = if recv { "RETR " } else if (flags & ZFTP_APPE) != 0 { "APPE " }
else { "STOR " };
let ttype = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.transfer_type))
.unwrap_or(ZFST_IMAG as i32);
zfsettype(ttype);
if recv { let _ = std::io::stdout().flush(); }
let mut i = 0usize;
while i < args.len() {
let arg = args[i];
let mut rest_cmd: String = String::new(); let mut startat: libc::off_t = 0;
if progress != 0 && crate::ported::utils::getshfunc("zftp_progress").is_some() {
let mut sz: libc::off_t = -1; let mut _mdtm: Option<String> = None;
let dumb = (zfprefs.load(std::sync::atomic::Ordering::Relaxed)
& ZFPF_DUMB) != 0; if !dumb && (recv || (flags & ZFTP_REST) == 0) { let _ = zfstats(arg, if recv { 1 } else { 0 }, &mut sz, &mut _mdtm, 0);
if recv && sz == -1 { getsize = 1; }
} else {
getsize = 1; }
zfstarttrans(arg, if recv { 1 } else { 0 }, sz); }
if (flags & ZFTP_REST) != 0 && i + 1 < args.len() {
startat = args[i + 1].parse().unwrap_or(0);
rest_cmd = format!("REST {}\r\n", args[i + 1]);
}
let ln = format!("{}{}\r\n", cmd_pfx, arg);
let gd = zfgetdata(name, &rest_cmd, &ln, getsize);
if gd != 0 {
ret = 2; } else {
if zfsenddata(name, if recv { 1 } else { 0 }, progress, startat) != 0 {
ret = 1; }
}
if progress != 0 && ret != 2 {
if let Some(_shfunc) = crate::ported::utils::getshfunc("zftp_progress") { let osc = crate::ported::builtin::SFCONTEXT.load(
std::sync::atomic::Ordering::Relaxed); zfsetparam("ZFTP_TRANSFER", if recv { "GF" } else { "PF" }, ZFPM_READONLY);
crate::ported::builtin::SFCONTEXT.store(
crate::ported::zsh_h::SFC_HOOK,
std::sync::atomic::Ordering::Relaxed); let _ = crate::ported::builtin::LASTVAL.load(
std::sync::atomic::Ordering::Relaxed);
crate::ported::builtin::SFCONTEXT.store(osc,
std::sync::atomic::Ordering::Relaxed); } else {
zfsetparam("ZFTP_TRANSFER", if recv { "GF" } else { "PF" }, ZFPM_READONLY);
}
}
if (flags & ZFTP_REST) != 0 {
i += 1;
}
if crate::ported::utils::errflag.load(std::sync::atomic::Ordering::Relaxed) != 0 {
break;
}
let _ = getsize;
getsize = 0; i += 1;
}
zfendtrans(); if ret != 0 { 1 } else { 0 } }
#[allow(unused_variables)]
pub fn zftp_delete(name: &str, args: &[&str], flags: i32) -> i32 { let mut ret: i32 = 0; let cmd: String; let _ = cmd;
for aptr in args.iter() { let cmd = format!("DELE {}\r\n", aptr); if zfsendcmd(&cmd) > 2 { ret = 1; }
}
ret }
#[allow(unused_variables)]
pub fn zftp_mkdir(name: &str, args: &[&str], flags: i32) -> i32 { let ret: i32; if args.is_empty() { return 1; }
let cmd_pfx = if (flags & ZFTP_DELE) != 0 { "RMD " } else { "MKD " }; let cmd = format!("{}{}\r\n", cmd_pfx, args[0]); ret = (zfsendcmd(&cmd) > 2) as i32; ret }
#[allow(unused_variables)]
pub fn zftp_rename(name: &str, args: &[&str], flags: i32) -> i32 { let mut ret: i32; let cmd: String; let _ = cmd;
if args.len() < 2 { return 1; }
let cmd = format!("RNFR {}\r\n", args[0]); ret = 1; if zfsendcmd(&cmd) == 3 { let cmd = format!("RNTO {}\r\n", args[1]); if zfsendcmd(&cmd) == 2 { ret = 0; }
}
ret }
#[allow(unused_variables)]
pub fn zftp_quote(name: &str, args: &[&str], flags: i32) -> i32 { let ret: i32; let cmd: String; let _ = cmd;
let argv: Vec<&str> = args.to_vec();
let cmd = if (flags & ZFTP_SITE) != 0 { zfargstring("SITE", &argv) + "\r\n"
} else {
if argv.is_empty() { return 1; }
zfargstring(argv[0], &argv[1..]) + "\r\n" };
ret = (zfsendcmd(&cmd) > 2) as i32; ret }
#[allow(non_snake_case)]
pub fn zfclose(leaveparams: i32) { let alive = zftp_state().lock().ok()
.and_then(|s| s.get_session(None).map(|sess| sess.control.is_some()))
.unwrap_or(false);
if !alive { return; }
ZFCLOSING.store(1, Ordering::Relaxed); if ZCFINISH.load(Ordering::Relaxed) != 2 { let _ = zfsendcmd("QUIT\r\n"); }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.cin = None; sess.control = None; sess.dfd = -1;
sess.connected = false;
sess.logged_in = false;
}
}
ZFNOPEN.fetch_sub(1, Ordering::Relaxed);
if leaveparams == 0 { for n in ["ZFTP_HOST", "ZFTP_PORT", "ZFTP_IP", "ZFTP_SYSTEM", "ZFTP_USER", "ZFTP_ACCOUNT", "ZFTP_PWD",
"ZFTP_TYPE", "ZFTP_MODE"] {
zfunsetparam(n); }
if crate::ported::utils::getshfunc("zftp_chpwd").is_some() { let osc = crate::ported::builtin::SFCONTEXT.load(Ordering::Relaxed);
crate::ported::builtin::SFCONTEXT.store(
crate::ported::zsh_h::SFC_HOOK, Ordering::Relaxed); let _ = crate::ported::builtin::LASTVAL.load(Ordering::Relaxed);
crate::ported::builtin::SFCONTEXT.store(osc, Ordering::Relaxed); }
}
ZFCLOSING.store(0, Ordering::Relaxed); ZFDRRRRING.store(0, Ordering::Relaxed); }
#[allow(unused_variables)]
pub fn zftp_close(name: &str, args: &[&str], flags: i32) -> i32 { zfclose(0); 0 }
#[allow(non_snake_case)]
pub fn newsession(nm: &str) -> Box<zftp_session> {
Box::new(zftp_session::new(nm))
}
#[allow(non_snake_case)]
pub fn savesession() { let val: String;
let _ = val;
if let Ok(mut state) = zftp_state().lock() {
let sess = match state.get_session_mut(None) {
Some(s) => s,
None => return,
};
sess.params.clear();
for ps in ZFPARAMS { let val = crate::ported::params::getsparam(ps).unwrap_or_default();
sess.params.push(val);
}
}
}
#[allow(non_snake_case)]
pub fn switchsession(nm: &str) {
if let Ok(mut state) = zftp_state().lock() {
let _ = state.create_session(nm);
state.set_current(nm);
}
}
#[allow(non_snake_case)]
pub fn freesession(sptr: &mut zftp_session) { sptr.name.clear();
sptr.params.clear();
sptr.userparams.clear();
}
#[allow(unused_variables)]
pub fn zftp_rmsession(name: &str, args: &[&str], flags: i32) -> i32 { let mut found_name: Option<String> = None; let mut is_current: bool = false; let mut newsess: Option<String> = None;
{
let state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 1,
};
let current = state.current_name().unwrap_or("default").to_string();
let target = args.first().copied().map(String::from).unwrap_or_else(|| current.clone());
for sess_name in state.session_names() { if sess_name == target {
found_name = Some(sess_name.to_string());
is_current = sess_name == current;
break;
}
}
}
let target_name = match found_name { Some(n) => n,
None => return 1, };
if is_current { zfclosedata(); zfclose(0); let other = zftp_state().lock().ok()
.map(|s| s.session_names().into_iter()
.find(|n| *n != target_name).map(String::from))
.unwrap_or(None);
if let Some(o) = other { newsess = Some(o);
}
} else { let prev = zftp_state().lock().ok()
.and_then(|s| s.current_name().map(String::from));
if let Ok(mut state) = zftp_state().lock() {
state.set_current(&target_name); }
zfclosedata(); zfclose(1); if let (Some(p), Ok(mut state)) = (prev, zftp_state().lock()) {
state.set_current(&p); }
}
if let Ok(mut state) = zftp_state().lock() {
state.remove_session(&target_name);
}
if let Some(n) = newsess { switchsession(&n); } else if zftp_state().lock().map(|s| s.session_names().is_empty()).unwrap_or(false) {
newsession("default");
}
0 }
#[allow(non_snake_case)]
pub fn bin_zftp(_nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let argv: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
let mut zftp_guard = zftp_state().lock()
.unwrap_or_else(|e| { zftp_state_clear_poison(); e.into_inner() });
let zftp = &mut *zftp_guard;
let args = &argv[..];
let (status, output): (i32, String) = (|| {
if args.is_empty() {
return (1, "zftp: subcommand required\n".to_string());
}
match args[0] {
"open" => {
if args.len() < 2 {
return (1, "zftp open: host required\n".to_string());
}
let host = args[1];
let port: Option<u16> = args.get(2).and_then(|s| s.parse().ok());
let session_name = zftp.current_name().unwrap_or("default").to_string();
let sess = zftp.create_session(&session_name);
match sess.connect(host, port) {
Ok(resp) => {
if (resp.0 >= 100 && resp.0 < 400) {
zftp.set_current(&session_name);
(0, resp.1)
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp open: {}\n", e)),
}
}
"login" | "user" => {
if args.len() < 2 {
return (1, "zftp login: user required\n".to_string());
}
let user = args[1];
let pass = args.get(2).copied();
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp login: not connected\n".to_string()),
};
match sess.login(user, pass) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, resp.1)
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp login: {}\n", e)),
}
}
"cd" => {
if args.len() < 2 {
return (1, "zftp cd: path required\n".to_string());
}
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp cd: not connected\n".to_string()),
};
match sess.cd(args[1]) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, resp.1)
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp cd: {}\n", e)),
}
}
"cdup" => {
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp cdup: not connected\n".to_string()),
};
match sess.cdup() {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, resp.1)
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp cdup: {}\n", e)),
}
}
"pwd" => {
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp pwd: not connected\n".to_string()),
};
match sess.pwd() {
Ok((resp, pwd)) => {
if let Some(p) = pwd {
(0, format!("{}\n", p))
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp pwd: {}\n", e)),
}
}
"dir" | "ls" => {
let path = args.get(1).copied();
let use_nlst = args[0] == "ls";
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp dir: not connected\n".to_string()),
};
let result = if use_nlst {
sess.nlst(path)
} else {
sess.list(path)
};
match result {
Ok((resp, lines)) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, lines.join("\n") + "\n")
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp dir: {}\n", e)),
}
}
"get" => {
if args.len() < 2 {
return (1, "zftp get: remote file required\n".to_string());
}
let remote = args[1];
let local = args.get(2).unwrap_or(&remote);
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp get: not connected\n".to_string()),
};
match sess.get(remote, Path::new(local)) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp get: {}\n", e)),
}
}
"put" => {
if args.len() < 2 {
return (1, "zftp put: local file required\n".to_string());
}
let local = args[1];
let remote = args.get(2).unwrap_or(&local);
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp put: not connected\n".to_string()),
};
match sess.put(Path::new(local), remote) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp put: {}\n", e)),
}
}
"delete" => {
if args.len() < 2 {
return (1, "zftp delete: file required\n".to_string());
}
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp delete: not connected\n".to_string()),
};
match sess.delete(args[1]) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp delete: {}\n", e)),
}
}
"mkdir" => {
if args.len() < 2 {
return (1, "zftp mkdir: directory required\n".to_string());
}
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp mkdir: not connected\n".to_string()),
};
match sess.mkdir(args[1]) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp mkdir: {}\n", e)),
}
}
"rmdir" => {
if args.len() < 2 {
return (1, "zftp rmdir: directory required\n".to_string());
}
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp rmdir: not connected\n".to_string()),
};
match sess.rmdir(args[1]) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp rmdir: {}\n", e)),
}
}
"rename" => {
if args.len() < 3 {
return (1, "zftp rename: from and to required\n".to_string());
}
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp rename: not connected\n".to_string()),
};
match sess.rename(args[1], args[2]) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp rename: {}\n", e)),
}
}
"type" | "ascii" | "binary" => {
let transfer_type = match args[0] {
"ascii" => ZFST_ASCI,
"binary" => ZFST_IMAG,
"type" => {
if args.len() < 2 {
let sess = match zftp.get_session(None) {
Some(s) => s,
None => return (1, "zftp type: not connected\n".to_string()),
};
return (
0,
format!(
"{}\n",
if sess.transfer_type == ZFST_ASCI {
"ascii"
} else {
"binary"
}
),
);
}
match args[1].to_lowercase().as_str() {
"a" | "ascii" => ZFST_ASCI,
"i" | "binary" | "image" => ZFST_IMAG,
_ => return (1, format!("zftp type: unknown type {}\n", args[1])),
}
}
_ => unreachable!(),
};
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp type: not connected\n".to_string()),
};
match sess.set_type(transfer_type) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp type: {}\n", e)),
}
}
"bslashquote" => {
if args.len() < 2 {
return (1, "zftp bslashquote: command required\n".to_string());
}
let cmd = args[1..].join(" ");
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp bslashquote: not connected\n".to_string()),
};
match sess.bslashquote(&cmd) {
Ok(resp) => (if (resp.0 >= 100 && resp.0 < 400) { 0 } else { 1 }, resp.1),
Err(e) => (1, format!("zftp bslashquote: {}\n", e)),
}
}
"close" | "quit" => {
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (0, String::new()),
};
match sess.close() {
Ok(_) => (0, String::new()),
Err(e) => (1, format!("zftp close: {}\n", e)),
}
}
"session" => {
if args.len() < 2 {
let names = zftp.session_names();
let current = zftp.current_name();
let mut out = String::new();
for name in names {
let marker = if Some(name) == current { "* " } else { " " };
out.push_str(&format!("{}{}\n", marker, name));
}
return (0, out);
}
let name = args[1];
if zftp.sessions.contains_key(name) {
zftp.set_current(name);
} else {
zftp.create_session(name);
zftp.set_current(name);
}
(0, String::new())
}
"rmsession" => {
if args.len() < 2 {
return (1, "zftp rmsession: session name required\n".to_string());
}
if zftp.remove_session(args[1]).is_some() {
(0, String::new())
} else {
(
1,
format!("zftp rmsession: session {} not found\n", args[1]),
)
}
}
"test" => {
let sess = zftp.get_session(None);
if sess.map(|s| s.connected).unwrap_or(false) {
(0, String::new())
} else {
(1, String::new())
}
}
_ => (1, format!("zftp: unknown subcommand {}\n", args[0])),
}
})();
drop(zftp_guard);
if !output.is_empty() {
if status == 0 { print!("{}", output); } else { eprint!("{}", output); }
}
status
}
pub fn zftp_cleanup() -> i32 { let cursess = zftp_state().lock().ok()
.and_then(|s| s.current_name().map(|n| n.to_string()));
let session_names: Vec<String> = zftp_state().lock().ok()
.map(|s| s.session_names().iter().map(|n| n.to_string()).collect())
.unwrap_or_default();
for nm in session_names { if let Ok(mut s) = zftp_state().lock() {
let _ = s.set_current(&nm);
}
zfclosedata(); let leaveparams = if Some(&nm) != cursess.as_ref() { 1 } else { 0 };
zfclose(leaveparams); }
if let Ok(mut m) = lastmsg.lock() {
m.clear(); }
zfunsetparam("ZFTP_SESSION"); if let Ok(mut state) = zftp_state().lock() {
*state = zftp_globals::new(); }
0
}
impl zftp_session {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(), params: Vec::new(), userparams: Vec::new(), cin: None, control: None, dfd: -1, has_size: 0, has_mdtm: 0, host: None,
port: 21,
user: None,
pwd: None,
connected: false,
logged_in: false,
transfer_type: ZFST_IMAG,
transfer_mode: ZFST_STRE,
passive: true,
syst_probed: false,
}
}
fn send_command(&mut self, cmd: &str) -> io::Result<()> {
if let Some(ref mut stream) = self.cin {
write!(stream, "{}\r\n", cmd)?;
stream.flush()
} else {
Err(io::Error::new(io::ErrorKind::NotConnected, "not connected"))
}
}
fn read_response(&mut self) -> io::Result<FtpResponse> {
let stream = self
.cin
.as_mut()
.ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "not connected"))?;
let mut reader = BufReader::new(stream.try_clone()?);
let mut full_message = String::new();
let mut code = 0u32;
let mut multiline = false;
let mut first_code = String::new();
loop {
let mut line = String::new();
reader.read_line(&mut line)?;
let line = line.trim_end();
if line.len() < 3 {
continue;
}
if code == 0 {
first_code = line[..3].to_string();
code = first_code.parse().unwrap_or(0);
if line.len() > 3 && line.chars().nth(3) == Some('-') {
multiline = true;
}
}
full_message.push_str(line);
full_message.push('\n');
if multiline {
if line.starts_with(&first_code)
&& line.len() > 3
&& line.chars().nth(3) == Some(' ')
{
break;
}
} else {
break;
}
}
Ok((code as i32, full_message))
}
pub fn connect(&mut self, host: &str, port: Option<u16>) -> io::Result<FtpResponse> {
let port = port.unwrap_or(21);
let addr_str = format!("{}:{}", host, port);
let dns_timeout = Duration::from_secs(10);
let (tx, rx) = std::sync::mpsc::channel();
let dns = addr_str.clone();
std::thread::Builder::new()
.name("zftp-dns".to_string())
.spawn(move || {
let _ = tx.send(dns.to_socket_addrs().map(|a| a.collect::<Vec<_>>()));
})
.map_err(io::Error::other)?;
let addrs = rx
.recv_timeout(dns_timeout)
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "DNS resolution timed out"))?
.map_err(|e| {
crate::ported::utils::zwarnnam(
"zftp",
&format!("host not found: {}: {}", host, e),
);
e
})?;
let sock_addr = addrs
.into_iter()
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid address"))?;
let stream = TcpStream::connect_timeout(&sock_addr, Duration::from_secs(30))?;
stream.set_read_timeout(Some(Duration::from_secs(60)))?;
stream.set_write_timeout(Some(Duration::from_secs(60)))?;
self.cin = Some(stream);
self.host = Some(host.to_string());
self.port = port;
self.connected = true;
self.read_response()
}
pub fn login(&mut self, user: &str, pass: Option<&str>) -> io::Result<FtpResponse> {
self.send_command(&format!("USER {}", user))?;
let resp = self.read_response()?;
if resp.0 == 331 {
let password = pass.unwrap_or("");
self.send_command(&format!("PASS {}", password))?;
let resp = self.read_response()?;
if (resp.0 >= 200 && resp.0 < 300) {
self.logged_in = true;
self.user = Some(user.to_string());
}
return Ok(resp);
}
if (resp.0 >= 200 && resp.0 < 300) {
self.logged_in = true;
self.user = Some(user.to_string());
}
Ok(resp)
}
pub fn set_type(&mut self, transfer_type: i32) -> io::Result<FtpResponse> {
let typ_letter = if (transfer_type & ZFST_IMAG) != 0 { "I" } else { "A" };
self.send_command(&format!("TYPE {}", typ_letter))?;
let resp = self.read_response()?;
if (resp.0 >= 200 && resp.0 < 300) {
self.transfer_type = transfer_type;
}
Ok(resp)
}
pub fn cd(&mut self, path: &str) -> io::Result<FtpResponse> {
self.send_command(&format!("CWD {}", path))?;
self.read_response()
}
pub fn cdup(&mut self) -> io::Result<FtpResponse> {
self.send_command("CDUP")?;
self.read_response()
}
pub fn pwd(&mut self) -> io::Result<(FtpResponse, Option<String>)> {
self.send_command("PWD")?;
let resp = self.read_response()?;
let pwd = if (resp.0 >= 200 && resp.0 < 300) {
if let Some(start) = resp.1.find('"') {
if let Some(end) = resp.1[start + 1..].find('"') {
Some(resp.1[start + 1..start + 1 + end].to_string())
} else {
None
}
} else {
None
}
} else {
None
};
Ok((resp, pwd))
}
pub fn list(&mut self, path: Option<&str>) -> io::Result<(FtpResponse, Vec<String>)> {
let data_stream = self.enter_passive_mode()?;
let cmd = match path {
Some(p) => format!("LIST {}", p),
None => "LIST".to_string(),
};
self.send_command(&cmd)?;
let resp = self.read_response()?;
if !(resp.0 >= 100 && resp.0 < 400) {
return Ok((resp, Vec::new()));
}
let mut reader = BufReader::new(data_stream);
let mut lines = Vec::new();
let mut line = String::new();
while reader.read_line(&mut line)? > 0 {
lines.push(line.trim_end().to_string());
line.clear();
}
let final_resp = self.read_response()?;
Ok((final_resp, lines))
}
pub fn nlst(&mut self, path: Option<&str>) -> io::Result<(FtpResponse, Vec<String>)> {
let data_stream = self.enter_passive_mode()?;
let cmd = match path {
Some(p) => format!("NLST {}", p),
None => "NLST".to_string(),
};
self.send_command(&cmd)?;
let resp = self.read_response()?;
if !(resp.0 >= 100 && resp.0 < 400) {
return Ok((resp, Vec::new()));
}
let mut reader = BufReader::new(data_stream);
let mut lines = Vec::new();
let mut line = String::new();
while reader.read_line(&mut line)? > 0 {
lines.push(line.trim_end().to_string());
line.clear();
}
let final_resp = self.read_response()?;
Ok((final_resp, lines))
}
fn enter_passive_mode(&mut self) -> io::Result<TcpStream> {
self.send_command("PASV")?;
let resp = self.read_response()?;
if !(resp.0 >= 200 && resp.0 < 300) {
return Err(io::Error::other(resp.1));
}
let (ip, port) = parse_pasv_response(&resp.1)?;
let addr = format!("{}:{}", ip, port);
TcpStream::connect_timeout(
&addr.to_socket_addrs()?.next().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "invalid PASV address")
})?,
Duration::from_secs(30),
)
}
pub fn get(&mut self, remote: &str, local: &Path) -> io::Result<FtpResponse> {
let mut data_stream = self.enter_passive_mode()?;
self.send_command(&format!("RETR {}", remote))?;
let resp = self.read_response()?;
if !(resp.0 >= 100 && resp.0 < 400) {
return Ok(resp);
}
let mut file = std::fs::File::create(local)?;
let mut buf = [0u8; 8192];
loop {
let n = data_stream.read(&mut buf)?;
if n == 0 {
break;
}
file.write_all(&buf[..n])?;
}
self.read_response()
}
pub fn put(&mut self, local: &Path, remote: &str) -> io::Result<FtpResponse> {
let mut data_stream = self.enter_passive_mode()?;
self.send_command(&format!("STOR {}", remote))?;
let resp = self.read_response()?;
if !(resp.0 >= 100 && resp.0 < 400) {
return Ok(resp);
}
let mut file = std::fs::File::open(local)?;
let mut buf = [0u8; 8192];
loop {
let n = file.read(&mut buf)?;
if n == 0 {
break;
}
data_stream.write_all(&buf[..n])?;
}
drop(data_stream);
self.read_response()
}
pub fn delete(&mut self, path: &str) -> io::Result<FtpResponse> {
self.send_command(&format!("DELE {}", path))?;
self.read_response()
}
pub fn mkdir(&mut self, path: &str) -> io::Result<FtpResponse> {
self.send_command(&format!("MKD {}", path))?;
self.read_response()
}
pub fn rmdir(&mut self, path: &str) -> io::Result<FtpResponse> {
self.send_command(&format!("RMD {}", path))?;
self.read_response()
}
pub fn rename(&mut self, from: &str, to: &str) -> io::Result<FtpResponse> {
self.send_command(&format!("RNFR {}", from))?;
let resp = self.read_response()?;
if !(resp.0 >= 300 && resp.0 < 400) {
return Ok(resp);
}
self.send_command(&format!("RNTO {}", to))?;
self.read_response()
}
pub fn size(&mut self, path: &str) -> io::Result<(FtpResponse, Option<u64>)> {
self.send_command(&format!("SIZE {}", path))?;
let resp = self.read_response()?;
let size = if (resp.0 >= 200 && resp.0 < 300) {
resp.1
.split_whitespace()
.last()
.and_then(|s| s.parse().ok())
} else {
None
};
Ok((resp, size))
}
pub fn bslashquote(&mut self, cmd: &str) -> io::Result<FtpResponse> {
self.send_command(cmd)?;
self.read_response()
}
pub fn close(&mut self) -> io::Result<FtpResponse> {
if !self.connected {
return Ok((0, "not connected".to_string()));
}
let resp = if let Ok(()) = self.send_command("QUIT") {
self.read_response().unwrap_or_else(|_| (221, "Goodbye".to_string()))
} else {
(221, "Goodbye".to_string())
};
self.cin = None;
self.connected = false;
self.logged_in = false;
self.host = None;
self.user = None;
self.pwd = None;
Ok(resp)
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn zftpexithook(d: *const crate::ported::zsh_h::hookdef, dummy: *mut std::ffi::c_void) -> i32 {
zftp_cleanup(); 0 }
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 { 0
}
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 {
*features = featuresarray(m, module_features());
0
}
impl zftp_globals {
pub fn new() -> Self {
Self::default()
}
pub fn get_session(&self, name: Option<&str>) -> Option<&zftp_session> {
let key = name
.map(|s| s.to_string())
.or_else(|| self.current.clone())?;
self.sessions.get(&key)
}
pub fn get_session_mut(&mut self, name: Option<&str>) -> Option<&mut zftp_session> {
let key = name
.map(|s| s.to_string())
.or_else(|| self.current.clone())?;
self.sessions.get_mut(&key)
}
pub fn create_session(&mut self, name: &str) -> &mut zftp_session {
self.sessions
.entry(name.to_string())
.or_insert_with(|| zftp_session::new(name))
}
pub fn remove_session(&mut self, name: &str) -> Option<zftp_session> {
let sess = self.sessions.remove(name);
if self.current.as_deref() == Some(name) {
let mut keys: Vec<&String> = self.sessions.keys().collect();
keys.sort();
self.current = keys.first().map(|s| (*s).clone());
}
sess
}
pub fn set_current(&mut self, name: &str) -> bool {
if self.sessions.contains_key(name) {
self.current = Some(name.to_string());
true
} else {
false
}
}
pub fn current_name(&self) -> Option<&str> {
self.current.as_deref()
}
pub fn session_names(&self) -> Vec<&str> {
let mut names: Vec<&str> = self.sessions.keys().map(|s| s.as_str()).collect();
names.sort();
names
}
}
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
handlefeatures(m, module_features(), enables)
}
use crate::ported::zsh_h::module;
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 { zfsetparam("ZFTP_VERBOSE", "450", ZFPM_IFUNSET); zfsetparam("ZFTP_TMOUT", "60", ZFPM_IFUNSET | ZFPM_INTEGER); zfsetparam("ZFTP_PREFS", "PS", ZFPM_IFUNSET); zfprefs.store(ZFPF_SNDP | ZFPF_PASV, std::sync::atomic::Ordering::Relaxed);
let _default = newsession("default"); 0
}
pub fn cleanup_(m: *const module) -> i32 { zftp_cleanup(); setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 { 0
}
pub static lastmsg: std::sync::Mutex<String> = std::sync::Mutex::new(String::new());
pub static lastcodestr: std::sync::Mutex<[u8; 4]>
= std::sync::Mutex::new([b'0', b'0', b'0', 0]);
#[allow(non_camel_case_types)]
pub type FtpResponse = (i32, String);
pub const ZFHD_MARK: i32 = 16; pub const ZFHD_ERRS: i32 = 32; pub const ZFHD_EOFB: i32 = 64; pub const ZFHD_EORB: i32 = 128;
#[allow(non_camel_case_types)]
pub type readwrite_t = fn(i32, &mut [u8], libc::off_t, i32) -> i32;
pub const ZFTP_CONN: i32 = 0x0001; pub const ZFTP_LOGI: i32 = 0x0002; pub const ZFTP_TBIN: i32 = 0x0004; pub const ZFTP_TASC: i32 = 0x0008; pub const ZFTP_NLST: i32 = 0x0010; pub const ZFTP_DELE: i32 = 0x0020; pub const ZFTP_SITE: i32 = 0x0040; pub const ZFTP_APPE: i32 = 0x0080; pub const ZFTP_HERE: i32 = 0x0100; pub const ZFTP_CDUP: i32 = 0x0200; pub const ZFTP_REST: i32 = 0x0400; pub const ZFTP_RECV: i32 = 0x0800; pub const ZFTP_TEST: i32 = 0x1000; pub const ZFTP_SESS: i32 = 0x2000;
pub static ZFPARAMS: &[&str] = &[
"ZFTP_HOST", "ZFTP_PORT", "ZFTP_IP", "ZFTP_SYSTEM", "ZFTP_USER",
"ZFTP_ACCOUNT", "ZFTP_PWD", "ZFTP_TYPE", "ZFTP_MODE", ];
pub const ZFPM_READONLY: i32 = 0x01; pub const ZFPM_IFUNSET: i32 = 0x02; pub const ZFPM_INTEGER: i32 = 0x04;
pub static ZFNOPEN: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static ZCFINISH: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static ZFCLOSING: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub const ZFCP_UNKN: i32 = 0;
pub const ZFCP_YUPP: i32 = 1;
pub const ZFCP_NOPE: i32 = 2;
pub const ZFST_ASCI: i32 = 0x0000;
pub const ZFST_IMAG: i32 = 0x0001;
pub const ZFST_TMSK: i32 = 0x0001;
pub const ZFST_TBIT: i32 = 0x0001;
pub const ZFST_CASC: i32 = 0x0000;
pub const ZFST_CIMA: i32 = 0x0002;
pub const ZFST_STRE: i32 = 0x0000;
pub const ZFST_BLOC: i32 = 0x0004;
pub const ZFST_MMSK: i32 = 0x0004;
pub const ZFST_LOGI: i32 = 0x0008;
pub const ZFST_SYST: i32 = 0x0010;
pub const ZFST_NOPS: i32 = 0x0020;
pub const ZFST_NOSZ: i32 = 0x0040;
pub const ZFST_TRSZ: i32 = 0x0080;
pub const ZFST_CLOS: i32 = 0x0100;
pub const ZFPF_SNDP: i32 = 0x01; pub const ZFPF_PASV: i32 = 0x02; pub const ZFPF_DUMB: i32 = 0x04;
#[allow(non_camel_case_types)]
#[derive(Debug, Default)]
pub struct zftp_globals {
sessions: HashMap<String, zftp_session>,
current: Option<String>,
}
static ZFTP_STATE_INNER: std::sync::OnceLock<std::sync::Mutex<zftp_globals>> = std::sync::OnceLock::new();
#[inline]
fn errno_ptr() -> *mut libc::c_int {
#[cfg(any(target_os = "linux", target_os = "android"))]
unsafe { libc::__errno_location() }
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))]
unsafe { libc::__error() }
#[cfg(any(target_os = "openbsd", target_os = "netbsd"))]
unsafe {
extern "C" { fn __errno() -> *mut libc::c_int; }
__errno()
}
}
fn parse_pasv_response(msg: &str) -> io::Result<(String, u16)> { let start = msg
.find('(')
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid PASV response"))?;
let end = msg
.find(')')
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid PASV response"))?;
let nums: Vec<i32> = msg[start + 1..end]
.split(',')
.filter_map(|s| s.trim().parse().ok())
.collect();
if nums.len() != 6 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid PASV numbers",
));
}
let oct = |n: i32| -> u8 { n as u8 };
let ip = format!("{}.{}.{}.{}", oct(nums[0]), oct(nums[1]), oct(nums[2]), oct(nums[3]));
let port = ((oct(nums[4]) as u16) << 8) | (oct(nums[5]) as u16);
Ok((ip, port))
}
pub fn zftp_state_clear_poison() {
if let Some(m) = ZFTP_STATE_INNER.get() {
m.clear_poison();
}
}
pub static ZFDRRRRING: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static ZFALARMED: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static OALREMAIN: std::sync::atomic::AtomicU32 =
std::sync::atomic::AtomicU32::new(0);
pub static OALTIME: std::sync::atomic::AtomicI64 =
std::sync::atomic::AtomicI64::new(0);
use crate::ported::zsh_h::features as features_t;
use std::sync::{Mutex, OnceLock};
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["b:zftp".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<features_t>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 1]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}
pub fn zftp_state() -> &'static std::sync::Mutex<zftp_globals> {
ZFTP_STATE_INNER.get_or_init(|| std::sync::Mutex::new(zftp_globals::new()))
}
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
bn_list: None,
bn_size: 1,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 0,
n_abstract: 0,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transfer_type() {
let ascii_letter = if (ZFST_ASCI & ZFST_IMAG) != 0 { "I" } else { "A" };
let image_letter = if (ZFST_IMAG & ZFST_IMAG) != 0 { "I" } else { "A" };
assert_eq!(ascii_letter, "A");
assert_eq!(image_letter, "I");
}
#[test]
fn test_transfer_mode() {
let stream_letter = if (ZFST_STRE & ZFST_BLOC) != 0 { "B" } else { "S" };
let block_letter = if (ZFST_BLOC & ZFST_BLOC) != 0 { "B" } else { "S" };
assert_eq!(stream_letter, "S");
assert_eq!(block_letter, "B");
}
fn is_positive(c: i32) -> bool { c >= 100 && c < 400 }
fn is_positive_completion(c: i32) -> bool { c >= 200 && c < 300 }
fn is_positive_intermediate(c: i32) -> bool { c >= 300 && c < 400 }
fn is_negative(c: i32) -> bool { c >= 400 }
#[test]
fn test_ftp_response_positive() {
let resp: FtpResponse = (200, "OK".to_string());
assert!(is_positive(resp.0));
assert!(is_positive_completion(resp.0));
assert!(!is_negative(resp.0));
}
#[test]
fn test_ftp_response_intermediate() {
let resp: FtpResponse = (331, "Password required".to_string());
assert!(is_positive(resp.0));
assert!(is_positive_intermediate(resp.0));
assert!(!is_positive_completion(resp.0));
}
#[test]
fn test_ftp_response_negative() {
let resp: FtpResponse = (550, "File not found".to_string());
assert!(is_negative(resp.0));
assert!(!is_positive(resp.0));
}
#[test]
fn test_ftp_session_new() {
let sess = zftp_session::new("test");
assert_eq!(sess.name, "test");
assert!(!sess.connected);
assert!(!sess.logged_in);
}
#[test]
fn test_parse_pasv_response() {
let msg = "227 Entering Passive Mode (192,168,1,1,4,1)";
let (ip, port) = parse_pasv_response(msg).unwrap();
assert_eq!(ip, "192.168.1.1");
assert_eq!(port, 1025);
}
#[test]
fn test_parse_pasv_response_invalid() {
let msg = "invalid";
assert!(parse_pasv_response(msg).is_err());
}
#[test]
fn test_zftp_new() {
let zftp = zftp_globals::new();
assert!(zftp.session_names().is_empty());
}
#[test]
fn test_zftp_create_session() {
let mut zftp = zftp_globals::new();
zftp.create_session("test");
assert!(zftp.sessions.contains_key("test"));
}
#[test]
fn test_zftp_remove_session() {
let mut zftp = zftp_globals::new();
zftp.create_session("test");
assert!(zftp.remove_session("test").is_some());
assert!(zftp.remove_session("test").is_none());
}
#[test]
fn test_zftp_set_current() {
let mut zftp = zftp_globals::new();
zftp.create_session("test");
assert!(zftp.set_current("test"));
assert!(!zftp.set_current("nonexistent"));
}
#[test]
fn test_builtin_zftp_no_args() {
let mut zftp = zftp_globals::new();
let status = bin_zftp("zftp", &[].iter().map(|s: &&str| s.to_string()).collect::<Vec<_>>(), &crate::ported::zsh_h::options { ind: [0u8; crate::ported::zsh_h::MAX_OPS], args: Vec::new(), argscount: 0, argsalloc: 0 }, 0);
assert_eq!(status, 1);
}
#[test]
fn test_builtin_zftp_session() {
zftp_cleanup();
let status = bin_zftp("zftp", &["session", "test"].iter().map(|s: &&str| s.to_string()).collect::<Vec<_>>(), &crate::ported::zsh_h::options { ind: [0u8; crate::ported::zsh_h::MAX_OPS], args: Vec::new(), argscount: 0, argsalloc: 0 }, 0);
assert_eq!(status, 0);
assert!(zftp_state().lock().unwrap().sessions.contains_key("test"));
zftp_cleanup();
}
#[test]
fn test_builtin_zftp_test_not_connected() {
let mut zftp = zftp_globals::new();
let status = bin_zftp("zftp", &["test"].iter().map(|s: &&str| s.to_string()).collect::<Vec<_>>(), &crate::ported::zsh_h::options { ind: [0u8; crate::ported::zsh_h::MAX_OPS], args: Vec::new(), argscount: 0, argsalloc: 0 }, 0);
assert_eq!(status, 1);
}
fn zftp_empty_ops() -> crate::ported::zsh_h::options {
crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(), argscount: 0, argsalloc: 0,
}
}
#[test]
fn zftp_unknown_subcommand_returns_one() {
zftp_cleanup();
let ops = zftp_empty_ops();
let r = bin_zftp("zftp",
&["definitely_not_a_subcommand_xyz".to_string()], &ops, 0);
assert_eq!(r, 1);
zftp_cleanup();
}
#[test]
fn zftp_state_empty_after_cleanup() {
zftp_cleanup();
assert!(zftp_state().lock().unwrap().sessions.is_empty());
}
#[test]
fn zfst_type_isolates_transfer_type_bit() {
assert_eq!(ZFST_TYPE(ZFST_ASCI), 0);
assert_eq!(ZFST_TYPE(ZFST_IMAG), 1);
assert_eq!(ZFST_TYPE(ZFST_IMAG | ZFST_BLOC), 1);
assert_eq!(ZFST_TYPE(ZFST_IMAG | ZFST_LOGI | ZFST_SYST), 1);
assert_eq!(ZFST_TYPE(ZFST_LOGI | ZFST_SYST | ZFST_BLOC), 0);
}
#[test]
fn zfst_mode_isolates_transfer_mode_bit() {
assert_eq!(ZFST_MODE(ZFST_STRE), 0);
assert_eq!(ZFST_MODE(ZFST_BLOC), 4);
assert_eq!(ZFST_MODE(ZFST_BLOC | ZFST_IMAG), 4);
assert_eq!(ZFST_MODE(ZFST_BLOC | ZFST_LOGI | ZFST_SYST), 4);
let many = ZFST_LOGI | ZFST_SYST | ZFST_NOPS | ZFST_NOSZ | ZFST_TRSZ | ZFST_CLOS;
assert_eq!(ZFST_MODE(many), 0);
}
#[test]
fn zfargstring_empty_args_returns_cmd() {
assert_eq!(zfargstring("RETR", &[]), "RETR");
assert_eq!(zfargstring("QUIT", &[]), "QUIT");
assert_eq!(zfargstring("", &[]), "");
}
#[test]
fn zfargstring_single_arg_one_space() {
assert_eq!(zfargstring("RETR", &["file.txt"]), "RETR file.txt");
assert_eq!(zfargstring("USER", &["anonymous"]), "USER anonymous");
}
#[test]
fn zfargstring_multi_arg_space_joined() {
assert_eq!(
zfargstring("USER", &["anonymous", "pass@example.com"]),
"USER anonymous pass@example.com"
);
assert_eq!(
zfargstring("PORT", &["192", "168", "1", "1", "4", "1"]),
"PORT 192 168 1 1 4 1"
);
}
#[test]
fn zfargstring_empty_arg_emits_space() {
assert_eq!(zfargstring("CMD", &["", "after"]), "CMD after");
assert_eq!(zfargstring("CMD", &["before", ""]), "CMD before ");
}
#[test]
fn zfst_type_constants_are_zero_and_one() {
assert_eq!(ZFST_ASCI, 0);
assert_eq!(ZFST_IMAG, 1);
assert_eq!(ZFST_TMSK, 1);
assert_eq!(ZFST_ASCI | ZFST_IMAG, ZFST_TMSK);
}
#[test]
fn zfst_mode_constants_have_correct_bit_position() {
assert_eq!(ZFST_STRE, 0);
assert_eq!(ZFST_BLOC, 0x04);
assert_eq!(ZFST_MMSK, 0x04);
assert_eq!(ZFST_STRE | ZFST_BLOC, ZFST_MMSK);
assert_eq!(ZFST_TMSK & ZFST_MMSK, 0,
"c:3907/3919 — type and mode bit-fields must be disjoint");
}
#[test]
fn zfst_status_flag_bits_are_pairwise_distinct() {
let flags = [ZFST_LOGI, ZFST_SYST, ZFST_NOPS, ZFST_NOSZ, ZFST_TRSZ, ZFST_CLOS];
let mut sorted = flags.to_vec();
sorted.sort();
sorted.dedup();
assert_eq!(sorted.len(), flags.len(),
"ZFST_* status flags must be pairwise distinct");
for f in flags {
assert_eq!(f & ZFST_TMSK, 0,
"ZFST flag 0x{:x} must not overlap type mask", f);
assert_eq!(f & ZFST_MMSK, 0,
"ZFST flag 0x{:x} must not overlap mode mask", f);
}
}
#[test]
fn parse_pasv_response_well_formed_round_trips() {
let (ip, port) = parse_pasv_response(
"227 Entering Passive Mode (192,168,1,1,4,1)").unwrap();
assert_eq!(ip, "192.168.1.1");
assert_eq!(port, 4 * 256 + 1, "p1*256+p2 = 1025");
let (_, port) = parse_pasv_response(
"227 ok (10,0,0,1,255,255)").unwrap();
assert_eq!(port, 65535);
let (_, port) = parse_pasv_response(
"227 ok (127,0,0,1,0,21)").unwrap();
assert_eq!(port, 21);
}
#[test]
fn parse_pasv_response_out_of_range_octet_truncates_low_byte() {
let (_, port) = parse_pasv_response(
"227 ok (192,168,1,1,300,1)").unwrap();
assert_eq!(port, 44 * 256 + 1,
"c:949 — (unsigned char)300 = 44; port = 44*256+1");
let (_, port) = parse_pasv_response(
"227 ok (1,2,3,4,1000,2000)").unwrap();
let expected_p1 = (1000i32 as u8) as u16; let expected_p2 = (2000i32 as u8) as u16; assert_eq!(port, (expected_p1 << 8) | expected_p2);
}
#[test]
fn parse_pasv_response_ip_octets_truncate_to_u8() {
let (ip, _) = parse_pasv_response("227 ok (300,168,1,1,0,21)").unwrap();
assert_eq!(ip, "44.168.1.1",
"c:947 — octets truncate; out-of-range becomes low byte");
}
#[test]
fn parse_pasv_response_wrong_number_count_errors() {
assert!(parse_pasv_response("227 ok (1,2,3,4)").is_err(),
"only 4 numbers → error");
assert!(parse_pasv_response("227 ok (1,2,3,4,5)").is_err(),
"only 5 numbers → error");
assert!(parse_pasv_response("227 ok (1,2,3,4,5,6,7)").is_err(),
"7 numbers → error");
}
#[test]
fn parse_pasv_response_missing_parens_errors() {
assert!(parse_pasv_response("227 ok no parens here").is_err(),
"missing both parens → error");
assert!(parse_pasv_response("227 ok (no_close").is_err(),
"missing close paren → error");
assert!(parse_pasv_response("227 ok no_open)").is_err(),
"missing open paren → error");
}
}