diff --git a/Cargo.lock b/Cargo.lock index 1f99179..445f193 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,18 +9,47 @@ dependencies = [ "memchr", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "bumpalo" version = "3.4.0" @@ -79,6 +108,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "core-foundation" version = "0.9.1" @@ -95,6 +130,17 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "lazy_static", +] + [[package]] name = "crossterm" version = "0.18.2" @@ -120,6 +166,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -501,6 +567,17 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] + [[package]] name = "regex" version = "1.4.2" @@ -538,6 +615,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "schannel" version = "0.1.19" @@ -577,6 +666,26 @@ dependencies = [ "libc", ] +[[package]] +name = "serde" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "signal-hook" version = "0.1.16" @@ -665,15 +774,18 @@ dependencies = [ "bytesize", "chrono", "crossterm", + "dirs", "ftp4", "getopts", "hostname", "lazy_static", "regex", "rpassword", + "serde", "ssh2", "tempfile", "textwrap", + "toml", "tui", "unicode-width", "users", @@ -710,6 +822,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "toml" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" +dependencies = [ + "serde", +] + [[package]] name = "tui" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index dd2be4c..cc36ef2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,20 +15,23 @@ readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bytesize = "1.0.1" +chrono = "0.4.19" crossterm = "0.18.2" +dirs = "3.0.1" ftp4 = { version = "^4.0.1", features = ["secure"] } getopts = "0.2.21" -ssh2 = "0.9.0" -tui = { version = "0.13.0", features = ["crossterm"], default-features = false } -whoami = "1.0.0" -rpassword = "5.0.0" -unicode-width = "0.1.7" -chrono = "0.4.19" -bytesize = "1.0.1" -textwrap = "0.13.0" -regex = "1.4.2" -lazy_static = "1.4.0" hostname = "0.3.1" +lazy_static = "1.4.0" +regex = "1.4.2" +rpassword = "5.0.0" +serde = { version = "1.0.118", features = ["derive"] } +ssh2 = "0.9.0" +textwrap = "0.13.0" +toml = "0.5.7" +tui = { version = "0.13.0", features = ["crossterm"], default-features = false } +unicode-width = "0.1.7" +whoami = "1.0.0" [target.'cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))'.dependencies] users = "0.11.0" diff --git a/src/bookmarks/mod.rs b/src/bookmarks/mod.rs new file mode 100644 index 0000000..d1dca75 --- /dev/null +++ b/src/bookmarks/mod.rs @@ -0,0 +1,103 @@ +//! ## Bookmarks +//! +//! `bookmarks` is the module which provides data types and de/serializer for bookmarks + +/* +* +* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "TermSCP" +* +* TermSCP is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* TermSCP is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with TermSCP. If not, see . +* +*/ + +pub mod serializer; + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Deserialize, Serialize, std::fmt::Debug)] +/// ## UserHosts +/// +/// UserHosts contains all the hosts saved by the user in the data storage +/// It contains both `Bookmark` +pub struct UserHosts { + pub bookmarks: HashMap, + pub recents: HashMap, +} + +#[derive(Deserialize, Serialize, std::fmt::Debug)] +/// ## Bookmark +/// +/// Bookmark describes a single bookmark entry in the user hosts storage +pub struct Bookmark { + pub address: String, + pub port: u16, + pub protocol: String, + pub username: String, +} + +// Errors + +/// ## SerializerError +/// +/// Contains the error for serializer/deserializer +#[derive(std::fmt::Debug)] +pub struct SerializerError { + kind: SerializerErrorKind, + msg: Option, +} + +/// ## SerializerErrorKind +/// +/// Describes the kind of error for the serializer/deserializer +#[derive(std::fmt::Debug)] +pub enum SerializerErrorKind { + IoError, + SerializationError, + SyntaxError, +} + +impl SerializerError { + /// ### new + /// + /// Instantiate a new `SerializerError` + pub fn new(kind: SerializerErrorKind) -> SerializerError { + SerializerError { kind, msg: None } + } + + /// ### new_ex + /// + /// Instantiates a new `SerializerError` with description message + pub fn new_ex(kind: SerializerErrorKind, msg: String) -> SerializerError { + let mut err: SerializerError = SerializerError::new(kind); + err.msg = Some(msg); + err + } +} + +impl std::fmt::Display for SerializerError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let err: String = match &self.kind { + SerializerErrorKind::IoError => String::from("IO Error"), + SerializerErrorKind::SerializationError => String::from("Serialization error"), + SerializerErrorKind::SyntaxError => String::from("Syntax error"), + }; + match &self.msg { + Some(msg) => write!(f, "{} ({})", err, msg), + None => write!(f, "{}", err), + } + } +} diff --git a/src/bookmarks/serializer.rs b/src/bookmarks/serializer.rs new file mode 100644 index 0000000..acb5cd5 --- /dev/null +++ b/src/bookmarks/serializer.rs @@ -0,0 +1,213 @@ +//! ## Serializer +//! +//! `serializer` is the module which provides the serializer/deserializer for bookmarks + +/* +* +* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "TermSCP" +* +* TermSCP is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* TermSCP is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with TermSCP. If not, see . +* +*/ + +use super::{SerializerError, SerializerErrorKind, UserHosts}; + +use std::io::{Read, Write}; + +pub struct BookmarkSerializer {} + +impl BookmarkSerializer { + /// ### serialize + /// + /// Serialize `UserHosts` into TOML and write content to writable + pub fn serialize( + &self, + mut writable: Box, + hosts: &UserHosts, + ) -> Result<(), SerializerError> { + // Serialize content + let data: String = match toml::ser::to_string(hosts) { + Ok(dt) => dt, + Err(err) => { + return Err(SerializerError::new_ex( + SerializerErrorKind::SerializationError, + err.to_string(), + )) + } + }; + // Write file + match writable.write_all(data.as_bytes()) { + Ok(_) => Ok(()), + Err(err) => Err(SerializerError::new_ex( + SerializerErrorKind::IoError, + err.to_string(), + )), + } + } + + /// ### deserialize + /// + /// Read data from readable and deserialize its content as TOML + pub fn deserialize(&self, mut readable: Box) -> Result { + // Read file content + let mut data: String = String::new(); + if let Err(err) = readable.read_to_string(&mut data) { + return Err(SerializerError::new_ex( + SerializerErrorKind::IoError, + err.to_string(), + )); + } + // Deserialize + match toml::de::from_str(data.as_str()) { + Ok(hosts) => Ok(hosts), + Err(err) => Err(SerializerError::new_ex( + SerializerErrorKind::SyntaxError, + err.to_string(), + )), + } + } +} + +// Tests + +#[cfg(test)] +mod tests { + + use super::super::Bookmark; + use super::*; + + use std::collections::HashMap; + use std::io::{Seek, SeekFrom}; + + #[test] + fn test_bookmarks_serializer_deserialize_ok() { + let toml_file: tempfile::NamedTempFile = create_good_toml(); + toml_file.as_file().sync_all().unwrap(); + toml_file.as_file().seek(SeekFrom::Start(0)).unwrap(); + // Parse + let deserializer: BookmarkSerializer = BookmarkSerializer {}; + let hosts = deserializer.deserialize(Box::new(toml_file)); + assert!(hosts.is_ok()); + let hosts: UserHosts = hosts.ok().unwrap(); + // Verify hosts + // Verify recents + assert_eq!(hosts.recents.len(), 1); + let host: &Bookmark = hosts.recents.get("ISO20201215T094000Z").unwrap(); + assert_eq!(host.address, String::from("172.16.104.10")); + assert_eq!(host.port, 22); + assert_eq!(host.protocol, String::from("SCP")); + assert_eq!(host.username, String::from("root")); + // Verify bookmarks + assert_eq!(hosts.bookmarks.len(), 3); + let host: &Bookmark = hosts.bookmarks.get("raspberrypi2").unwrap(); + assert_eq!(host.address, String::from("192.168.1.31")); + assert_eq!(host.port, 22); + assert_eq!(host.protocol, String::from("SFTP")); + assert_eq!(host.username, String::from("root")); + let host: &Bookmark = hosts.bookmarks.get("msi-estrem").unwrap(); + assert_eq!(host.address, String::from("192.168.1.30")); + assert_eq!(host.port, 22); + assert_eq!(host.protocol, String::from("SFTP")); + assert_eq!(host.username, String::from("cvisintin")); + let host: &Bookmark = hosts.bookmarks.get("aws-server-prod1").unwrap(); + assert_eq!(host.address, String::from("51.23.67.12")); + assert_eq!(host.port, 21); + assert_eq!(host.protocol, String::from("FTPS")); + assert_eq!(host.username, String::from("aws001")); + } + + #[test] + fn test_bookmarks_serializer_deserialize_nok() { + let toml_file: tempfile::NamedTempFile = create_bad_toml(); + toml_file.as_file().sync_all().unwrap(); + toml_file.as_file().seek(SeekFrom::Start(0)).unwrap(); + // Parse + let deserializer: BookmarkSerializer = BookmarkSerializer {}; + assert!(deserializer.deserialize(Box::new(toml_file)).is_err()); + } + + #[test] + fn test_bookmarks_serializer_serialize() { + let mut bookmarks: HashMap = HashMap::with_capacity(2); + // Push two samples + bookmarks.insert( + String::from("raspberrypi2"), + Bookmark { + address: String::from("192.168.1.31"), + port: 22, + protocol: String::from("SFTP"), + username: String::from("root"), + }, + ); + bookmarks.insert( + String::from("msi-estrem"), + Bookmark { + address: String::from("192.168.1.30"), + port: 4022, + protocol: String::from("SFTP"), + username: String::from("cvisintin"), + }, + ); + let mut recents: HashMap = HashMap::with_capacity(1); + recents.insert( + String::from("ISO20201215T094000Z"), + Bookmark { + address: String::from("192.168.1.254"), + port: 3022, + protocol: String::from("SCP"), + username: String::from("omar"), + }, + ); + let tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); + // Serialize + let deserializer: BookmarkSerializer = BookmarkSerializer {}; + let hosts: UserHosts = UserHosts { bookmarks, recents }; + assert!(deserializer.serialize(Box::new(tmpfile), &hosts).is_ok()); + } + + fn create_good_toml() -> tempfile::NamedTempFile { + // Write + let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); + let file_content: &str = r#" + [bookmarks] + raspberrypi2 = { address = "192.168.1.31", port = 22, protocol = "SFTP", username = "root" } + msi-estrem = { address = "192.168.1.30", port = 22, protocol = "SFTP", username = "cvisintin" } + aws-server-prod1 = { address = "51.23.67.12", port = 21, protocol = "FTPS", username = "aws001" } + + [recents] + ISO20201215T094000Z = { address = "172.16.104.10", port = 22, protocol = "SCP", username = "root" } + "#; + tmpfile.write_all(file_content.as_bytes()).unwrap(); + //write!(tmpfile, "[bookmarks]\nraspberrypi2 = {{ address = \"192.168.1.31\", port = 22, protocol = \"SFTP\", username = \"root\" }}\nmsi-estrem = {{ address = \"192.168.1.30\", port = 22, protocol = \"SFTP\", username = \"cvisintin\" }}\naws-server-prod1 = {{ address = \"51.23.67.12\", port = 21, protocol = \"FTPS\", username = \"aws001\" }}\n\n[recents]\nISO20201215T094000Z = {{ address = \"172.16.104.10\", port = 22, protocol = \"SCP\", username = \"root\" }}\n"); + tmpfile + } + + fn create_bad_toml() -> tempfile::NamedTempFile { + // Write + let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); + let file_content: &str = r#" + [bookmarks] + raspberrypi2 = { address = "192.168.1.31", port = 22, protocol = "SFTP", username = "root"} + msi-estrem = { address = "192.168.1.30", port = 22, protocol = "SFTP" } + aws-server-prod1 = { address = "51.23.67.12", port = 21, protocol = "FTPS", username = "aws001" } + + [recents] + ISO20201215T094000Z = { address = "172.16.104.10", protocol = "SCP", username = "root", port = 22 } + "#; + tmpfile.write_all(file_content.as_bytes()).unwrap(); + tmpfile + } +} diff --git a/src/lib.rs b/src/lib.rs index 6c82b9d..f40a0c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ #[macro_use] extern crate lazy_static; pub mod activity_manager; +pub mod bookmarks; pub mod filetransfer; pub mod fs; pub mod host;