diff --git a/Cargo.lock b/Cargo.lock index 1b60398..5d0f821 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "wasi", +] + [[package]] name = "instant" version = "0.1.8" @@ -222,12 +233,68 @@ dependencies = [ "winapi", ] +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -272,12 +339,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termscp" version = "0.1.0" dependencies = [ "crossterm 0.18.2", "getopts", + "tempfile", "tui", "users", ] @@ -317,6 +399,12 @@ dependencies = [ "log", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 7e79e06..1bfb083 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ getopts = "0.2.21" tui = { version = "0.12.0", features = ["crossterm"], default-features = false } users = "0.11.0" +[dev-dependencies] +tempfile = "3" + [[bin]] name = "termscp" path = "src/main.rs" diff --git a/src/fs/mod.rs b/src/fs/mod.rs index aabca24..54b5184 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -42,7 +42,7 @@ pub enum FsEntry { #[derive(Clone)] pub struct FsDirectory { - pub name: PathBuf, + pub name: String, pub last_change_time: SystemTime, pub last_access_time: SystemTime, pub creation_time: SystemTime, @@ -59,7 +59,7 @@ pub struct FsDirectory { #[derive(Clone)] pub struct FsFile { - pub name: PathBuf, + pub name: String, pub last_change_time: SystemTime, pub last_access_time: SystemTime, pub creation_time: SystemTime, diff --git a/src/host/mod.rs b/src/host/mod.rs index 9af9e77..48e14d2 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -23,14 +23,14 @@ * */ -use std::fs::{self, Metadata}; +use std::fs::{self, File, Metadata, OpenOptions}; use std::path::{Path, PathBuf}; use std::time::SystemTime; // Metadata ext #[cfg(any(unix, macos, linux))] extern crate users; #[cfg(any(unix, macos, linux))] -use std::os::unix::fs::{MetadataExt, PermissionsExt}; +use std::os::unix::fs::MetadataExt; #[cfg(any(unix, macos, linux))] use users::{get_group_by_gid, get_user_by_uid}; @@ -44,6 +44,7 @@ pub enum HostError { NoSuchFileOrDirectory, ReadonlyFile, DirNotAccessible, + FileNotAccessible, } /// ## Localhost @@ -113,6 +114,42 @@ impl Localhost { Ok(self.wrkdir.clone()) } + /// ### open_file_read + /// + /// Open file for read + pub fn open_file_read(&self, file: &Path) -> Result { + if !self.file_exists(file) { + return Err(HostError::NoSuchFileOrDirectory); + } + match OpenOptions::new() + .create(false) + .read(true) + .write(false) + .open(file) + { + Ok(f) => Ok(f), + Err(_) => Err(HostError::FileNotAccessible), + } + } + + /// ### open_file_write + /// + /// Open file for write + pub fn open_file_write(&self, file: &Path) -> Result { + match OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(file) + { + Ok(f) => Ok(f), + Err(_) => match self.file_exists(file) { + true => Err(HostError::ReadonlyFile), + false => Err(HostError::FileNotAccessible), + }, + } + } + /// ### file_exists /// /// Returns whether provided file path exists @@ -137,19 +174,21 @@ impl Localhost { let is_symlink: bool = attr.file_type().is_symlink(); // Get user stuff let user: Option = match get_user_by_uid(attr.uid()) { - Some(user) => String::from(user.name().to_str().unwrap_or("")), + Some(user) => Some(String::from(user.name().to_str().unwrap_or(""))), None => None, }; let group: Option = match get_group_by_gid(attr.gid()) { - Some(gruop) => String::from(group.name().to_str().unwrap_or("")), + Some(group) => Some(String::from(group.name().to_str().unwrap_or(""))), None => None, }; + let file_name: String = + String::from(path.file_name().unwrap().to_str().unwrap_or("")); // Match dir / file fs_entries.push(match path.is_dir() { true => { // Is dir FsEntry::Directory(FsDirectory { - name: path.file_name(), + name: file_name, last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), @@ -176,7 +215,7 @@ impl Localhost { None => None, }; FsEntry::File(FsFile { - name: path.file_name(), + name: file_name, last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), @@ -219,11 +258,13 @@ impl Localhost { if let Ok(entry) = entry { let path: PathBuf = entry.path(); let attr: Metadata = fs::metadata(path.clone()).unwrap(); + let file_name: String = + String::from(path.file_name().unwrap().to_str().unwrap_or("")); fs_entries.push(match path.is_dir() { true => { // Is dir FsEntry::Directory(FsDirectory { - name: path, + name: file_name, last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), @@ -241,7 +282,7 @@ impl Localhost { None => None, }; FsEntry::File(FsFile { - name: path, + name: file_name, last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), @@ -271,3 +312,159 @@ impl Localhost { (user, group, others) } } + +#[cfg(test)] +mod tests { + + use super::*; + use std::io::Write; + + #[cfg(any(unix, macos, linux))] + use std::os::unix::fs::PermissionsExt; + + #[test] + #[cfg(any(unix, macos, linux))] + fn test_host_localhost_new() { + let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap(); + assert_eq!(host.wrkdir, PathBuf::from("/bin")); + // Scan dir + let entries = std::fs::read_dir(PathBuf::from("/bin").as_path()).unwrap(); + let mut counter: usize = 0; + for _ in entries { + counter = counter + 1; + } + assert_eq!(host.files.len(), counter); + } + + #[test] + #[cfg(target_os = "windows")] + fn test_host_localhost_new() { + let host: Localhost = Localhost::new(PathBuf::from("C:\\")).ok().unwrap(); + assert_eq!(host.wrkdir, PathBuf::from("C:\\")); + // Scan dir + let entries = std::fs::read_dir(PathBuf::from("C:\\").as_path()).unwrap(); + let mut counter: usize = 0; + for _ in entries { + counter = counter + 1; + } + assert_eq!(host.files.len(), counter); + } + + #[test] + #[should_panic] + fn test_host_localhost_new_bad() { + Localhost::new(PathBuf::from("/omargabber/123/345")) + .ok() + .unwrap(); + } + + #[test] + #[cfg(any(unix, macos, linux))] + fn test_host_localhost_pwd() { + let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap(); + assert_eq!(host.pwd(), PathBuf::from("/bin")); + } + + #[test] + #[cfg(any(unix, macos, linux))] + fn test_host_localhost_list_files() { + let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap(); + // Scan dir + let entries = std::fs::read_dir(PathBuf::from("/bin").as_path()).unwrap(); + let mut counter: usize = 0; + for _ in entries { + counter = counter + 1; + } + assert_eq!(host.list_dir().len(), counter); + } + + #[test] + #[cfg(any(unix, macos, linux))] + fn test_host_localhost_change_dir() { + let mut host: Localhost = Localhost::new(PathBuf::from("/usr")).ok().unwrap(); + let new_dir: PathBuf = PathBuf::from("/usr"); + assert!(host.change_wrkdir(new_dir.clone()).is_ok()); + // Verify new files + // Scan dir + let entries = std::fs::read_dir(PathBuf::from(new_dir).as_path()).unwrap(); + let mut counter: usize = 0; + for _ in entries { + counter = counter + 1; + } + assert_eq!(host.files.len(), counter); + } + + #[test] + #[cfg(any(unix, macos, linux))] + #[should_panic] + fn test_host_localhost_change_dir_failed() { + let mut host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap(); + let new_dir: PathBuf = PathBuf::from("/omar/gabber/123/456"); + assert!(host.change_wrkdir(new_dir.clone()).is_ok()); + } + + #[test] + #[cfg(any(unix, macos, linux))] + fn test_host_localhost_open_read() { + let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap(); + // Create temp file + let file: tempfile::NamedTempFile = create_sample_file(); + assert!(host.open_file_read(file.path()).is_ok()); + } + + #[test] + #[cfg(any(unix, macos, linux))] + #[should_panic] + fn test_host_localhost_open_read_err_no_such_file() { + let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap(); + assert!(host + .open_file_read(PathBuf::from("/bin/foo-bar-test-omar-123-456-789.txt").as_path()) + .is_ok()); + } + + #[test] + #[cfg(any(unix, macos, linux))] + fn test_host_localhost_open_read_err_not_accessible() { + let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap(); + let file: tempfile::NamedTempFile = create_sample_file(); + //let mut perms = fs::metadata(file.path())?.permissions(); + fs::set_permissions(file.path(), PermissionsExt::from_mode(0o222)).unwrap(); + //fs::set_permissions(file.path(), perms)?; + assert!(host.open_file_read(file.path()).is_err()); + } + + #[test] + #[cfg(any(unix, macos, linux))] + fn test_host_localhost_open_write() { + let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap(); + // Create temp file + let file: tempfile::NamedTempFile = create_sample_file(); + assert!(host.open_file_write(file.path()).is_ok()); + } + + #[test] + #[cfg(any(unix, macos, linux))] + fn test_host_localhost_open_write_err() { + let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap(); + let file: tempfile::NamedTempFile = create_sample_file(); + //let mut perms = fs::metadata(file.path())?.permissions(); + fs::set_permissions(file.path(), PermissionsExt::from_mode(0o444)).unwrap(); + //fs::set_permissions(file.path(), perms)?; + assert!(host.open_file_write(file.path()).is_err()); + } + + /// ### create_sample_file + /// + /// Create a sample file + #[cfg(any(unix, macos, linux))] + fn create_sample_file() -> tempfile::NamedTempFile { + // Write + let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); + write!( + tmpfile, + "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nMauris ultricies consequat eros,\nnec scelerisque magna imperdiet metus.\n" + ) + .unwrap(); + tmpfile + } +}