mod actions;
mod args;
mod command;
use std::io;
use std::io::Write;
use std::str::FromStr;
use actions::*;
use args::Args;
use command::Command;
use env_logger::Builder as LogBuilder;
use log::LevelFilter;
use suppaftp::{FtpError, NativeTlsFtpStream as FtpStream};
const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
const APP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
fn usage() {
println!("Available commands:");
println!("APPE <file> <dest> Append content of local file `file` to `dest`");
println!("CDUP Go to parent directory");
println!("CONNECT <addr:port> Connect to remote host");
println!("CONNECT+S <addr:port> Connect to remote host using FTPS");
println!("CWD <dir> Change working directory");
println!("FEAT Get supported features on the server");
println!("HELP Print this help");
println!(
"LIST [dir] List files. If directory is not provided, current directory is used"
);
println!("LOGIN Login to remote");
println!("MDTM <file> Get modification time for `file`");
println!("MKDIR <dir> Create directory");
println!(
"MLSD [dir] List files in a machine-readable format. If directory is not provided, current directory is used"
);
println!(
"MLST [dir] List files in a machine-readable format. If directory is not provided, current directory is used"
);
println!("MODE <PASSIVE|EXTPASSIVE|ACTIVE> Set mode");
println!("NOOP Ping server");
println!("OPTS <feature-name> [feature-value] Set a feature on the server (e.g. OPTS UTF8 ON)");
println!("PUT <file> <dest> Upload local file `file` to `dest`");
println!("PWD Print working directory");
println!("QUIT Quit suppaftp");
println!("RENAME <source> <dest> Rename file `source` to `dest`");
println!("RETR <file> <dest> Download `file` to `dest`");
println!("RM <file> Remove file");
println!("RMDIR <dir> Remove directory");
println!("SIZE <file> Get `file` size");
}
fn input() -> Command {
loop {
print!(">> ");
let _ = io::stdout().flush();
let mut input: String = String::new();
if let Err(err) = io::stdin().read_line(&mut input) {
eprintln!("Failed to read stdin: {err}");
return Command::Quit;
}
if let Ok(cmd) = Command::from_str(input.as_str()) {
return cmd;
}
println!("Unknown command");
}
}
fn main() {
let args: Args = argh::from_env();
if args.version {
println!("suppaftp {APP_VERSION} - developed by {APP_AUTHORS}")
}
LogBuilder::new()
.filter_level(if args.debug {
LevelFilter::Trace
} else if args.verbose {
LevelFilter::Info
} else {
LevelFilter::Off
})
.init();
let mut ftp: Option<FtpStream> = None;
if let Some(host) = args.host {
perform(&mut ftp, Command::Connect(host, false));
}
loop {
match input() {
Command::Quit => {
quit(ftp);
break;
}
Command::Help => usage(),
cmd => perform(&mut ftp, cmd),
}
}
}
fn perform(ftp: &mut Option<FtpStream>, command: Command) {
match ftp {
Some(ftp) => perform_connected(ftp, command),
None => {
if let Some(stream) = perform_uninitialized(command) {
*ftp = Some(stream);
}
}
}
}
fn perform_uninitialized(command: Command) -> Option<FtpStream> {
match command {
Command::Connect(remote, secure) => connect(remote.as_str(), secure),
_ => {
eprintln!("Can't perform command: you must connect to remote first");
None
}
}
}
fn perform_connected(ftp: &mut FtpStream, command: Command) {
match command {
Command::Appe(src, dest) => appe(ftp, src.as_path(), dest.as_str()),
Command::Cdup => cdup(ftp),
Command::Connect(remote, secure) => {
if let Some(stream) = connect(remote.as_str(), secure) {
*ftp = stream;
}
}
Command::Cwd(dir) => cwd(ftp, dir.as_str()),
Command::List(p) => list(ftp, p.as_deref()),
Command::Feat => feat(ftp),
Command::Login => login(ftp),
Command::Mdtm(p) => mdtm(ftp, p.as_str()),
Command::Mlsd(p) => mlsd(ftp, p.as_deref()),
Command::Mlst(p) => mlst(ftp, p.as_deref()),
Command::Mkdir(p) => mkdir(ftp, p.as_str()),
Command::Mode(m) => set_mode(ftp, m),
Command::Noop => noop(ftp),
Command::Opts(feature, values) => opts(ftp, feature, values),
Command::Put(src, dest) => put(ftp, src.as_path(), dest.as_str()),
Command::Pwd => pwd(ftp),
Command::Rename(src, dest) => rename(ftp, src.as_str(), dest.as_str()),
Command::Retr(file, dest) => retr(ftp, file.as_str(), dest.as_path()),
Command::Rm(file) => rm(ftp, file.as_str()),
Command::Rmdir(file) => rmdir(ftp, file.as_str()),
Command::Size(file) => size(ftp, file.as_str()),
Command::Help | Command::Quit => {
eprintln!("Unexpected command in connected state");
}
}
}