diff --git a/Cargo.lock b/Cargo.lock index dcd96bd..772fc89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1409,6 +1409,7 @@ dependencies = [ "ureq", "users", "whoami", + "wildmatch", ] [[package]] @@ -1703,6 +1704,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wildmatch" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f44b95f62d34113cf558c93511ac93027e03e9c29a60dd0fd70e6e025c7270a" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 65006fd..48e2c5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ toml = "0.5.8" tui = { version = "0.14.0", features = ["crossterm"], default-features = false } ureq = { version = "2.0.2", features = ["json"] } whoami = "1.1.0" +wildmatch = "1.0.13" [target.'cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))'.dependencies] users = "0.11.0" diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index b6925b0..62afb44 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -976,7 +976,7 @@ mod tests { } #[test] - fn test_filetransfer_sftp_copy() { + fn test_filetransfer_ftp_copy() { let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); // Connect assert!(ftp @@ -1090,6 +1090,32 @@ mod tests { assert!(ftp.disconnect().is_ok()); } + #[test] + fn test_filetransfer_ftp_find() { + let mut client: FtpFileTransfer = FtpFileTransfer::new(false); + // Connect + assert!(client + .connect( + String::from("test.rebex.net"), + 21, + Some(String::from("demo")), + Some(String::from("password")) + ) + .is_ok()); + // Pwd + assert_eq!(client.pwd().ok().unwrap(), PathBuf::from("/")); + // Search for file (let's search for pop3-*.png); there should be 2 + let search_res: Vec = client.find("pop3-*.png").ok().unwrap(); + assert_eq!(search_res.len(), 2); + // verify names + assert_eq!(search_res[0].name.as_str(), "pop3-browser.png"); + assert_eq!(search_res[1].name.as_str(), "pop3-console-client.png"); + // Disconnect + assert!(client.disconnect().is_ok()); + // Verify err + assert!(client.find("pippo").is_err()); + } + #[test] fn test_filetransfer_ftp_uninitialized() { let file: FsFile = FsFile { diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index 8c2f1e3..a01ba75 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -23,12 +23,15 @@ * */ +// dependencies +extern crate wildmatch; +// locals +use crate::fs::{FsEntry, FsFile}; +// ext use std::io::{Read, Write}; use std::path::{Path, PathBuf}; - -use crate::fs::{FsEntry, FsFile}; - -// Transfers +use wildmatch::WildMatch; +// exports pub mod ftp_transfer; pub mod scp_transfer; pub mod sftp_transfer; @@ -229,6 +232,66 @@ pub trait FileTransfer { /// This mighe be necessary for some protocols. /// You must call this method each time you want to finalize the read of the remote file. fn on_recv(&mut self, readable: Box) -> Result<(), FileTransferError>; + + /// ### find + /// + /// Find files from current directory (in all subdirectories) whose name matches the provided search + /// Search supports wildcards ('?', '*') + fn find(&mut self, search: &str) -> Result, FileTransferError> { + match self.is_connected() { + true => { + // Starting from current directory, iter dir + match self.pwd() { + Ok(p) => self.iter_search(p.as_path(), &WildMatch::new(search)), + Err(err) => Err(err), + } + } + false => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), + } + } + + /// ### iter_search + /// + /// Search recursively in `dir` for file matching the wildcard. + /// NOTE: DON'T RE-IMPLEMENT THIS FUNCTION, unless the file transfer provides a faster way to do so + /// NOTE: don't call this method from outside; consider it as private + fn iter_search( + &mut self, + dir: &Path, + filter: &WildMatch, + ) -> Result, FileTransferError> { + let mut drained: Vec = Vec::new(); + // Scan directory + match self.list_dir(dir) { + Ok(entries) => { + /* For each entry: + - if is dir: call iter_search with `dir` + - push `iter_search` result to `drained` + - if is file: check if it matches `filter` + - if it matches `filter`: push to to filter + */ + for entry in entries.iter() { + match entry { + FsEntry::Directory(dir) => { + match self.iter_search(dir.abs_path.as_path(), filter) { + Ok(mut filtered) => drained.append(&mut filtered), + Err(err) => return Err(err), + } + } + FsEntry::File(file) => { + if filter.is_match(file.name.as_str()) { + drained.push(file.clone()); + } + } + } + } + Ok(drained) + } + Err(err) => Err(err), + } + } } // Traits diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index 03d4f96..42ad2c8 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -1056,6 +1056,31 @@ mod tests { assert!(client.disconnect().is_ok()); } + #[test] + fn test_filetransfer_scp_find() { + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); + assert!(client + .connect( + String::from("test.rebex.net"), + 22, + Some(String::from("demo")), + Some(String::from("password")) + ) + .is_ok()); + // Check session and scp + assert!(client.session.is_some()); + // Search for file (let's search for pop3-*.png); there should be 2 + let search_res: Vec = client.find("pop3-*.png").ok().unwrap(); + assert_eq!(search_res.len(), 2); + // verify names + assert_eq!(search_res[0].name.as_str(), "pop3-browser.png"); + assert_eq!(search_res[1].name.as_str(), "pop3-console-client.png"); + // Disconnect + assert!(client.disconnect().is_ok()); + // Verify err + assert!(client.find("pippo").is_err()); + } + #[test] fn test_filetransfer_scp_recv() { let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index d0b7b18..fd57d5c 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -603,15 +603,13 @@ impl FileTransfer for SftpFileTransfer { /// Execute a command on remote host fn exec(&mut self, cmd: &str) -> Result { match self.is_connected() { - true => { - match self.perform_shell_cmd_with_path(cmd) { - Ok(output) => Ok(output), - Err(err) => Err(FileTransferError::new_ex( - FileTransferErrorType::ProtocolError, - format!("{}", err), - )), - } - } + true => match self.perform_shell_cmd_with_path(cmd) { + Ok(output) => Ok(output), + Err(err) => Err(FileTransferError::new_ex( + FileTransferErrorType::ProtocolError, + format!("{}", err), + )), + }, false => Err(FileTransferError::new( FileTransferErrorType::UninitializedSession, )), @@ -876,6 +874,9 @@ mod tests { .is_err()); // Disconnect assert!(client.disconnect().is_ok()); + assert!(client + .change_dir(PathBuf::from("gomar/pett").as_path()) + .is_err()); } #[test] @@ -899,6 +900,8 @@ mod tests { assert_eq!(files.len(), 3); // There are 3 files // Disconnect assert!(client.disconnect().is_ok()); + // Verify err + assert!(client.list_dir(pwd.as_path()).is_err()); } #[test] @@ -944,6 +947,33 @@ mod tests { assert_eq!(client.exec("echo 5").ok().unwrap().as_str(), "5\n"); // Disconnect assert!(client.disconnect().is_ok()); + // Verify err + assert!(client.exec("echo 1").is_err()); + } + + #[test] + fn test_filetransfer_sftp_find() { + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); + assert!(client + .connect( + String::from("test.rebex.net"), + 22, + Some(String::from("demo")), + Some(String::from("password")) + ) + .is_ok()); + // Check session and scp + assert!(client.session.is_some()); + // Search for file (let's search for pop3-*.png); there should be 2 + let search_res: Vec = client.find("pop3-*.png").ok().unwrap(); + assert_eq!(search_res.len(), 2); + // verify names + assert_eq!(search_res[0].name.as_str(), "pop3-browser.png"); + assert_eq!(search_res[1].name.as_str(), "pop3-console-client.png"); + // Disconnect + assert!(client.disconnect().is_ok()); + // Verify err + assert!(client.find("pippo").is_err()); } #[test]