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;