From 940d8d94e5911bf4038c9224d11b921d0aad22fb Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Tue, 15 Dec 2020 20:48:43 +0100 Subject: [PATCH 01/15] Moving bookmarks from ui to system --- src/lib.rs | 1 + src/main.rs | 1 + src/system/bookmarks_client.rs | 107 +++++++++++++++++++++++++++++++++ src/system/environment.rs | 68 +++++++++++++++++++++ src/system/mod.rs | 28 +++++++++ 5 files changed, 205 insertions(+) create mode 100644 src/system/bookmarks_client.rs create mode 100644 src/system/environment.rs create mode 100644 src/system/mod.rs diff --git a/src/lib.rs b/src/lib.rs index f40a0c2..419b65a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,5 +26,6 @@ pub mod bookmarks; pub mod filetransfer; pub mod fs; pub mod host; +pub mod system; pub mod ui; pub mod utils; diff --git a/src/main.rs b/src/main.rs index f41067d..afd0a06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,7 @@ mod bookmarks; mod filetransfer; mod fs; mod host; +mod system; mod ui; mod utils; diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs new file mode 100644 index 0000000..791cc92 --- /dev/null +++ b/src/system/bookmarks_client.rs @@ -0,0 +1,107 @@ +//! ## BookmarksClient +//! +//! `bookmarks_client` is the module which provides an API between the Bookmarks module and the system + +/* +* +* 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 . +* +*/ + +// Local +use crate::bookmarks::serializer::BookmarkSerializer; +use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts}; +use crate::filetransfer::FileTransferProtocol; +// Ext +use std::fs::OpenOptions; +use std::path::{Path, PathBuf}; + +/// ## BookmarksClient +/// +/// BookmarksClient provides a layer between the host system and the bookmarks module +pub struct BookmarksClient { + pub hosts: UserHosts, + bookmarks_file: PathBuf, + key_file: PathBuf, +} + +impl BookmarksClient { + /// ### BookmarksClient + /// + /// Instantiates a new BookmarksClient + /// Bookmarks file path must be provided + /// Key file must be provided + pub fn new(bookmarks_file: &Path, key_file: &Path) -> Result { + // Create default hosts + let default_hosts: UserHosts = Default::default(); + let client: BookmarksClient = BookmarksClient { + hosts: default_hosts, + bookmarks_file: PathBuf::from(bookmarks_file), + key_file: PathBuf::from(key_file), + }; + // If bookmark file doesn't exist, initialize it + if !bookmarks_file.exists() { + if let Err(err) = client.write_bookmarks() { + return Err(err) + } + } + // If key file doesn't exist, create key + if !key_file.exists() { + if let Err(err) = client.generate_key() { + return Err(err) + } + } + Ok(client) + } + + /// ### make_bookmark + /// + /// Make bookmark from credentials + pub fn make_bookmark(&self, addr: String, port: u16, protocol: FileTransferProtocol, username: String, password: Option) -> Bookmark { + // TODO: crypt password + } + + /// ### write_bookmarks + /// + /// Write bookmarks to file + pub fn write_bookmarks(&self) -> Result<(), SerializerError> { + // Open file + match OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(self.bookmarks_file.as_path()) + { + Ok(writer) => { + let serializer: BookmarkSerializer = BookmarkSerializer {}; + serializer.serialize(Box::new(writer), &self.hosts) + } + Err(err) => Err(SerializerError::new_ex( + SerializerErrorKind::IoError, + err.to_string(), + )), + } + } + + /// ### generate_key + /// + /// Generate a new AES key and write it to key file + fn generate_key(&self) -> Result<(), SerializerError> { + // TODO: write + } +} diff --git a/src/system/environment.rs b/src/system/environment.rs new file mode 100644 index 0000000..c4af3fe --- /dev/null +++ b/src/system/environment.rs @@ -0,0 +1,68 @@ +//! ## Environment +//! +//! `environment` is the module which provides Path and values for the system environment + +/* +* +* 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 . +* +*/ + +// Deps +extern crate dirs; + +// Ext +use std::path::PathBuf; + +/// ### get_config_dir +/// +/// Get termscp configuration directory path. +/// Returns None, if it's not possible to get it +pub fn init_config_dir() -> Result, String> { + // Get file + lazy_static! { + static ref CONF_DIR: Option = dirs::config_dir(); + } + if CONF_DIR.is_some() { + // Get path of bookmarks + let mut p: PathBuf = CONF_DIR.as_ref().unwrap().clone(); + // Append termscp dir + p.push("termscp/"); + // If directory doesn't exist, create it + match p.exists() { + true => Ok(Some(p)), + false => match std::fs::create_dir(p.as_path()) { + Ok(_) => Ok(Some(p)), + Err(err) => Err(err.to_string()), + }, + } + } else { + Ok(None) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_system_environment_get_config_dir() { + assert!(init_config_dir().ok().unwrap().is_some()); + } +} diff --git a/src/system/mod.rs b/src/system/mod.rs new file mode 100644 index 0000000..d0fe5be --- /dev/null +++ b/src/system/mod.rs @@ -0,0 +1,28 @@ +//! ## System +//! +//! `system` is the module which contains functions and data types related to current system + +/* +* +* 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 . +* +*/ + +// modules +pub mod bookmarks_client; +pub mod environment; From dcc289153fa736a4e3071ab641149afe3ef1af23 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Tue, 15 Dec 2020 22:17:19 +0100 Subject: [PATCH 02/15] Working on bookmarksClient --- Cargo.lock | 1 + Cargo.toml | 1 + src/system/bookmarks_client.rs | 115 +++++++++++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 445f193..9f9fc22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -779,6 +779,7 @@ dependencies = [ "getopts", "hostname", "lazy_static", + "rand", "regex", "rpassword", "serde", diff --git a/Cargo.toml b/Cargo.toml index cc36ef2..b729faa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ toml = "0.5.7" tui = { version = "0.13.0", features = ["crossterm"], default-features = false } unicode-width = "0.1.7" whoami = "1.0.0" +rand = "0.7.3" [target.'cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))'.dependencies] users = "0.11.0" diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index 791cc92..dcc683d 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -23,12 +23,17 @@ * */ +// Deps +extern crate rand; + // Local use crate::bookmarks::serializer::BookmarkSerializer; use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts}; use crate::filetransfer::FileTransferProtocol; // Ext -use std::fs::OpenOptions; +use rand::{distributions::Alphanumeric, Rng}; +use std::fs::{OpenOptions, Permissions}; +use std::io::Write; use std::path::{Path, PathBuf}; /// ## BookmarksClient @@ -57,23 +62,89 @@ impl BookmarksClient { // If bookmark file doesn't exist, initialize it if !bookmarks_file.exists() { if let Err(err) = client.write_bookmarks() { - return Err(err) + return Err(err); } } // If key file doesn't exist, create key if !key_file.exists() { if let Err(err) = client.generate_key() { - return Err(err) + return Err(err); } } Ok(client) } + /// ### get_bookmark + /// + /// Get bookmark associated to key + pub fn get_bookmark( + &self, + key: &String, + ) -> Option<(String, u16, FileTransferProtocol, String, Option)> { + let entry: &Bookmark = self.hosts.bookmarks.get(key)?; + // TODO: decrypt password + Some(( + entry.address.clone(), + entry.port, + match entry.protocol.to_ascii_uppercase().as_str() { + "FTP" => FileTransferProtocol::Ftp(false), + "FTPS" => FileTransferProtocol::Ftp(true), + "SCP" => FileTransferProtocol::Scp, + _ => FileTransferProtocol::Sftp, + }, + entry.username.clone(), + None, + )) + } + + /// ### get_recent + /// + /// Get recent associated to key + pub fn get_recent( + &self, + key: &String, + ) -> Option<(String, u16, FileTransferProtocol, String, Option)> { + let entry: &Bookmark = self.hosts.recents.get(key)?; + // TODO: decrypt password + Some(( + entry.address.clone(), + entry.port, + match entry.protocol.to_ascii_uppercase().as_str() { + "FTP" => FileTransferProtocol::Ftp(false), + "FTPS" => FileTransferProtocol::Ftp(true), + "SCP" => FileTransferProtocol::Scp, + _ => FileTransferProtocol::Sftp, + }, + entry.username.clone(), + None, + )) + } + /// ### make_bookmark - /// + /// /// Make bookmark from credentials - pub fn make_bookmark(&self, addr: String, port: u16, protocol: FileTransferProtocol, username: String, password: Option) -> Bookmark { + pub fn make_bookmark( + &self, + addr: String, + port: u16, + protocol: FileTransferProtocol, + username: String, + password: Option, + ) -> Bookmark { // TODO: crypt password + Bookmark { + address: addr, + port, + username, + protocol: match protocol { + FileTransferProtocol::Ftp(secure) => match secure { + true => String::from("FTPS"), + false => String::from("FTP"), + }, + FileTransferProtocol::Scp => String::from("SCP"), + FileTransferProtocol::Sftp => String::from("SFTP"), + }, + } } /// ### write_bookmarks @@ -99,9 +170,39 @@ impl BookmarksClient { } /// ### generate_key - /// + /// /// Generate a new AES key and write it to key file fn generate_key(&self) -> Result<(), SerializerError> { - // TODO: write + // Generate 256 bytes (2048 bits) key + let key: String = rand::thread_rng() + .sample_iter(Alphanumeric) + .take(256) + .collect::(); + // Write file + match OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(self.key_file.as_path()) + { + Ok(mut file) => { + // Write key to file + if let Err(err) = file.write_all(key.as_bytes()) { + return Err(SerializerError::new_ex( + SerializerErrorKind::IoError, + err.to_string(), + )); + } + // Set file to readonly + let mut permissions: Permissions = file.metadata().unwrap().permissions(); + permissions.set_readonly(true); + let _ = file.set_permissions(permissions); + Ok(()) + } + Err(err) => Err(SerializerError::new_ex( + SerializerErrorKind::IoError, + err.to_string(), + )), + } } } From ee55d1fd3146b16069c6c04f973840f94cedad88 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 12:09:34 +0100 Subject: [PATCH 03/15] Bookmarks client --- src/system/bookmarks_client.rs | 217 +++++++++++++++++++++++++-------- 1 file changed, 164 insertions(+), 53 deletions(-) diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index dcc683d..f22426c 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -24,17 +24,21 @@ */ // Deps +extern crate magic_crypt; extern crate rand; // Local use crate::bookmarks::serializer::BookmarkSerializer; use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts}; use crate::filetransfer::FileTransferProtocol; +use crate::utils::time_to_str; // Ext +use magic_crypt::MagicCryptTrait; use rand::{distributions::Alphanumeric, Rng}; use std::fs::{OpenOptions, Permissions}; -use std::io::Write; +use std::io::{Read, Write}; use std::path::{Path, PathBuf}; +use std::time::SystemTime; /// ## BookmarksClient /// @@ -42,7 +46,7 @@ use std::path::{Path, PathBuf}; pub struct BookmarksClient { pub hosts: UserHosts, bookmarks_file: PathBuf, - key_file: PathBuf, + key: String, } impl BookmarksClient { @@ -54,10 +58,21 @@ impl BookmarksClient { pub fn new(bookmarks_file: &Path, key_file: &Path) -> Result { // Create default hosts let default_hosts: UserHosts = Default::default(); + // If key file doesn't exist, create key, otherwise read it + let key: String = match key_file.exists() { + true => match BookmarksClient::load_key(key_file) { + Ok(key) => key, + Err(err) => return Err(err), + }, + false => match BookmarksClient::generate_key(key_file) { + Ok(key) => key, + Err(err) => return Err(err), + }, + }; let client: BookmarksClient = BookmarksClient { hosts: default_hosts, bookmarks_file: PathBuf::from(bookmarks_file), - key_file: PathBuf::from(key_file), + key, }; // If bookmark file doesn't exist, initialize it if !bookmarks_file.exists() { @@ -65,12 +80,7 @@ impl BookmarksClient { return Err(err); } } - // If key file doesn't exist, create key - if !key_file.exists() { - if let Err(err) = client.generate_key() { - return Err(err); - } - } + // Load key Ok(client) } @@ -82,7 +92,6 @@ impl BookmarksClient { key: &String, ) -> Option<(String, u16, FileTransferProtocol, String, Option)> { let entry: &Bookmark = self.hosts.bookmarks.get(key)?; - // TODO: decrypt password Some(( entry.address.clone(), entry.port, @@ -93,58 +102,91 @@ impl BookmarksClient { _ => FileTransferProtocol::Sftp, }, entry.username.clone(), - None, - )) - } - - /// ### get_recent - /// - /// Get recent associated to key - pub fn get_recent( - &self, - key: &String, - ) -> Option<(String, u16, FileTransferProtocol, String, Option)> { - let entry: &Bookmark = self.hosts.recents.get(key)?; - // TODO: decrypt password - Some(( - entry.address.clone(), - entry.port, - match entry.protocol.to_ascii_uppercase().as_str() { - "FTP" => FileTransferProtocol::Ftp(false), - "FTPS" => FileTransferProtocol::Ftp(true), - "SCP" => FileTransferProtocol::Scp, - _ => FileTransferProtocol::Sftp, + match entry.password { + // Decrypted password if Some; if decryption fails return None + Some(pwd) => match self.decrypt_str(pwd.as_str()) { + Ok(decrypted_pwd) => Some(decrypted_pwd), + Err(_) => None, + }, + None => None, }, - entry.username.clone(), - None, )) } - /// ### make_bookmark + /// ### add_recent /// - /// Make bookmark from credentials - pub fn make_bookmark( - &self, + /// Add a new recent to bookmarks + pub fn add_bookmark( + &mut self, + name: String, addr: String, port: u16, protocol: FileTransferProtocol, username: String, password: Option, - ) -> Bookmark { - // TODO: crypt password - Bookmark { - address: addr, - port, - username, - protocol: match protocol { - FileTransferProtocol::Ftp(secure) => match secure { - true => String::from("FTPS"), - false => String::from("FTP"), - }, - FileTransferProtocol::Scp => String::from("SCP"), - FileTransferProtocol::Sftp => String::from("SFTP"), + ) { + // Make bookmark + let host: Bookmark = self.make_bookmark(addr, port, protocol, username, password); + self.hosts.bookmarks.insert(name, host); + } + + /// ### get_recent + /// + /// Get recent associated to key + pub fn get_recent(&self, key: &String) -> Option<(String, u16, FileTransferProtocol, String)> { + // NOTE: password is not decrypted; recents will never have password + let entry: &Bookmark = self.hosts.recents.get(key)?; + Some(( + entry.address.clone(), + entry.port, + match entry.protocol.to_ascii_uppercase().as_str() { + "FTP" => FileTransferProtocol::Ftp(false), + "FTPS" => FileTransferProtocol::Ftp(true), + "SCP" => FileTransferProtocol::Scp, + _ => FileTransferProtocol::Sftp, }, + entry.username.clone(), + )) + } + + /// ### add_recent + /// + /// Add a new recent to bookmarks + pub fn add_recent( + &mut self, + addr: String, + port: u16, + protocol: FileTransferProtocol, + username: String, + ) { + // Make bookmark + let host: Bookmark = self.make_bookmark(addr, port, protocol, username, None); + // Check if duplicated + for recent_host in self.hosts.recents.values() { + if *recent_host == host { + // Don't save duplicates + return; + } } + // If hosts size is bigger than 16; pop last + if self.hosts.recents.len() >= 16 { + let mut keys: Vec = Vec::with_capacity(self.hosts.recents.len()); + for key in self.hosts.recents.keys() { + keys.push(key.clone()); + } + // Sort keys; NOTE: most recent is the last element + keys.sort(); + // Delete keys starting from the last one + for key in keys.iter() { + let _ = self.hosts.recents.remove(key); + // If length is < 16; break + if self.hosts.recents.len() < 16 { + break; + } + } + } + let name: String = time_to_str(SystemTime::now(), "ISO%Y%m%dT%H%M%S"); + self.hosts.recents.insert(name, host); } /// ### write_bookmarks @@ -172,7 +214,7 @@ impl BookmarksClient { /// ### generate_key /// /// Generate a new AES key and write it to key file - fn generate_key(&self) -> Result<(), SerializerError> { + fn generate_key(key_file: &Path) -> Result { // Generate 256 bytes (2048 bits) key let key: String = rand::thread_rng() .sample_iter(Alphanumeric) @@ -183,7 +225,7 @@ impl BookmarksClient { .create(true) .write(true) .truncate(true) - .open(self.key_file.as_path()) + .open(key_file) { Ok(mut file) => { // Write key to file @@ -197,7 +239,7 @@ impl BookmarksClient { let mut permissions: Permissions = file.metadata().unwrap().permissions(); permissions.set_readonly(true); let _ = file.set_permissions(permissions); - Ok(()) + Ok(key) } Err(err) => Err(SerializerError::new_ex( SerializerErrorKind::IoError, @@ -205,4 +247,73 @@ impl BookmarksClient { )), } } + + /// ### make_bookmark + /// + /// Make bookmark from credentials + fn make_bookmark( + &self, + addr: String, + port: u16, + protocol: FileTransferProtocol, + username: String, + password: Option, + ) -> Bookmark { + Bookmark { + address: addr, + port, + username, + protocol: match protocol { + FileTransferProtocol::Ftp(secure) => match secure { + true => String::from("FTPS"), + false => String::from("FTP"), + }, + FileTransferProtocol::Scp => String::from("SCP"), + FileTransferProtocol::Sftp => String::from("SFTP"), + }, + password: match password { + Some(p) => Some(self.encrypt_str(p.as_str())), // Encrypt password if provided + None => None, + }, + } + } + + /// ### load_key + /// + /// Load key from key_file + fn load_key(key_file: &Path) -> Result { + match OpenOptions::new().read(true).open(key_file) { + Ok(mut file) => { + let mut key: String = String::with_capacity(256); + file.read_to_string(&mut key); + Ok(key) + } + Err(err) => Err(SerializerError::new_ex( + SerializerErrorKind::IoError, + err.to_string(), + )), + } + } + + /// ### encrypt_str + /// + /// Encrypt provided string using AES-128. Encrypted buffer is then converted to BASE64 + fn encrypt_str(&self, txt: &str) -> String { + let crypter = new_magic_crypt!(self.key.clone(), 128); + crypter.encrypt_str_to_base64(txt.to_string()) + } + + /// ### decrypt_str + /// + /// Decrypt provided string using AES-128 + fn decrypt_str(&self, secret: &str) -> Result { + let crypter = new_magic_crypt!(self.key.clone(), 128); + match crypter.decrypt_base64_to_string(secret.to_string()) { + Ok(txt) => Ok(txt), + Err(err) => Err(SerializerError::new_ex( + SerializerErrorKind::SyntaxError, + err.to_string(), + )), + } + } } From 8cb363795452337b3b022fce9d9bb627b74c6ab7 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 15:46:46 +0100 Subject: [PATCH 04/15] Magic-crypt --- Cargo.lock | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +- 2 files changed, 234 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9f9fc22..4dc5a68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,15 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aes-soft" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +dependencies = [ + "cipher", + "opaque-debug", +] + [[package]] name = "aho-corasick" version = "0.7.15" @@ -50,12 +60,75 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d70f2a8c3126a2aec089e0aebcd945607e1155bfb5b89682eddf43c3ce386718" +dependencies = [ + "block-padding 0.1.5", + "byte-tools 0.2.0", + "generic-array 0.11.1", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-modes" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" +dependencies = [ + "block-padding 0.2.1", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools 0.3.1", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "bumpalo" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" +[[package]] +name = "byte-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + [[package]] name = "bytesize" version = "1.0.1" @@ -99,6 +172,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -130,6 +212,21 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + +[[package]] +name = "crc-any" +version = "2.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3784befdf9469f4d51c69ef0b774f6a99de6bcc655285f746f16e0dd63d9007" +dependencies = [ + "debug-helper", +] + [[package]] name = "crossbeam-utils" version = "0.8.1" @@ -166,6 +263,41 @@ dependencies = [ "winapi", ] +[[package]] +name = "debug-helper" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8a5bb894f24f42c247f19b25928a88e31867c0f84552c05df41a9dd527435e" + +[[package]] +name = "des" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b24e7c748888aa2fa8bce21d8c64a52efc810663285315ac7476f7197a982fae" +dependencies = [ + "byteorder", + "cipher", + "opaque-debug", +] + +[[package]] +name = "digest" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" +dependencies = [ + "generic-array 0.9.0", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "dirs" version = "3.0.1" @@ -213,6 +345,34 @@ dependencies = [ "regex", ] +[[package]] +name = "generic-array" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8107dafa78c80c848b71b60133954b4a58609a3a1a5f9af037ecc7f67280f369" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getopts" version = "0.2.21" @@ -327,12 +487,41 @@ dependencies = [ "cfg-if 0.1.10", ] +[[package]] +name = "magic-crypt" +version = "3.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a01cf5086c27e3daff2a06886ab2fc44fe4fdec7d2df7a82e5329483011bfd7" +dependencies = [ + "aes-soft", + "base64", + "block-modes", + "crc-any", + "des", + "digest 0.7.6", + "digest 0.9.0", + "md-5", + "sha2", + "tiger-digest", +] + [[package]] name = "match_cfg" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "memchr" version = "2.3.4" @@ -408,6 +597,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.30" @@ -686,6 +881,19 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpuid-bool", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "signal-hook" version = "0.1.16" @@ -779,6 +987,7 @@ dependencies = [ "getopts", "hostname", "lazy_static", + "magic-crypt", "rand", "regex", "rpassword", @@ -812,6 +1021,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "tiger-digest" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68067e91b4b9bb2e1ce3dc55077c984bbe2fa2be65308264dab403c165257545" +dependencies = [ + "block-buffer 0.5.1", + "byte-tools 0.2.0", + "digest 0.7.6", +] + [[package]] name = "time" version = "0.1.44" @@ -845,6 +1065,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + [[package]] name = "unicode-segmentation" version = "1.7.1" @@ -879,6 +1105,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index b729faa..0f586b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ ftp4 = { version = "^4.0.1", features = ["secure"] } getopts = "0.2.21" hostname = "0.3.1" lazy_static = "1.4.0" +magic-crypt = "3.1.6" +rand = "0.7.3" regex = "1.4.2" rpassword = "5.0.0" serde = { version = "1.0.118", features = ["derive"] } @@ -32,7 +34,6 @@ toml = "0.5.7" tui = { version = "0.13.0", features = ["crossterm"], default-features = false } unicode-width = "0.1.7" whoami = "1.0.0" -rand = "0.7.3" [target.'cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))'.dependencies] users = "0.11.0" From 52df9bc73b884d60524dda8eee18a46da3ab2892 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 15:47:02 +0100 Subject: [PATCH 05/15] Macro use for magic-crypt --- src/lib.rs | 5 ++++- src/main.rs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 419b65a..9353ea5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,10 @@ * */ -#[macro_use] extern crate lazy_static; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate magic_crypt; pub mod activity_manager; pub mod bookmarks; diff --git a/src/main.rs b/src/main.rs index afd0a06..d339ecd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,8 @@ const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); extern crate getopts; #[macro_use] extern crate lazy_static; +#[macro_use] +extern crate magic_crypt; extern crate rpassword; // External libs From 443789698bb751061a43ef3fa5597df047cdf1d3 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 15:57:46 +0100 Subject: [PATCH 06/15] Copy trait for FileTransferProtocol --- src/filetransfer/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index 82af420..a53f40f 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -37,7 +37,7 @@ pub mod sftp_transfer; /// /// This enum defines the different transfer protocol available in TermSCP -#[derive(std::cmp::PartialEq, std::fmt::Debug, std::clone::Clone)] +#[derive(std::cmp::PartialEq, std::fmt::Debug, std::clone::Clone, Copy)] pub enum FileTransferProtocol { Sftp, Scp, From 10df5abae29ea6153d7f45913e8bc7d925e078b6 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 16:00:43 +0100 Subject: [PATCH 07/15] Added Option password to bookmarks --- src/bookmarks/mod.rs | 1 + src/bookmarks/serializer.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/bookmarks/mod.rs b/src/bookmarks/mod.rs index a3ed49b..2cd0cd0 100644 --- a/src/bookmarks/mod.rs +++ b/src/bookmarks/mod.rs @@ -47,6 +47,7 @@ pub struct Bookmark { pub port: u16, pub protocol: String, pub username: String, + pub password: Option, // Password is optional; base64, aes-128 encrypted password } // Errors diff --git a/src/bookmarks/serializer.rs b/src/bookmarks/serializer.rs index acb5cd5..ded9561 100644 --- a/src/bookmarks/serializer.rs +++ b/src/bookmarks/serializer.rs @@ -110,6 +110,7 @@ mod tests { assert_eq!(host.port, 22); assert_eq!(host.protocol, String::from("SCP")); assert_eq!(host.username, String::from("root")); + assert_eq!(host.password, None); // Verify bookmarks assert_eq!(hosts.bookmarks.len(), 3); let host: &Bookmark = hosts.bookmarks.get("raspberrypi2").unwrap(); @@ -117,16 +118,19 @@ mod tests { assert_eq!(host.port, 22); assert_eq!(host.protocol, String::from("SFTP")); assert_eq!(host.username, String::from("root")); + assert_eq!(*host.password.as_ref().unwrap(), String::from("mypassword")); 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")); + assert_eq!(*host.password.as_ref().unwrap(), String::from("mysecret")); 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")); + assert_eq!(host.password, None); } #[test] @@ -150,6 +154,7 @@ mod tests { port: 22, protocol: String::from("SFTP"), username: String::from("root"), + password: None, }, ); bookmarks.insert( @@ -159,6 +164,7 @@ mod tests { port: 4022, protocol: String::from("SFTP"), username: String::from("cvisintin"), + password: Some(String::from("password")), }, ); let mut recents: HashMap = HashMap::with_capacity(1); @@ -169,6 +175,7 @@ mod tests { port: 3022, protocol: String::from("SCP"), username: String::from("omar"), + password: Some(String::from("aaa")), }, ); let tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap(); @@ -183,8 +190,8 @@ mod tests { 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" } + raspberrypi2 = { address = "192.168.1.31", port = 22, protocol = "SFTP", username = "root", password = "mypassword" } + msi-estrem = { address = "192.168.1.30", port = 22, protocol = "SFTP", username = "cvisintin", password = "mysecret" } aws-server-prod1 = { address = "51.23.67.12", port = 21, protocol = "FTPS", username = "aws001" } [recents] From 14ddba022f33cc50a5a23717ebd778ec3d869f7a Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 16:01:29 +0100 Subject: [PATCH 08/15] Implemented BookmarkClient in AuthActivity --- src/ui/activities/auth_activity/bookmarks.rs | 376 ++++++------------- src/ui/activities/auth_activity/input.rs | 16 +- src/ui/activities/auth_activity/layout.rs | 62 ++- src/ui/activities/auth_activity/mod.rs | 12 +- 4 files changed, 173 insertions(+), 293 deletions(-) diff --git a/src/ui/activities/auth_activity/bookmarks.rs b/src/ui/activities/auth_activity/bookmarks.rs index 99211a4..eddc7cd 100644 --- a/src/ui/activities/auth_activity/bookmarks.rs +++ b/src/ui/activities/auth_activity/bookmarks.rs @@ -27,78 +27,31 @@ extern crate dirs; // Locals -use super::{AuthActivity, Color, FileTransferProtocol, InputMode, PopupType, UserHosts}; -use crate::bookmarks::serializer::BookmarkSerializer; -use crate::bookmarks::Bookmark; -use crate::utils::time_to_str; +use super::{AuthActivity, Color, DialogYesNoOption, InputMode, PopupType}; +use crate::system::bookmarks_client::BookmarksClient; +use crate::system::environment; // Ext use std::path::PathBuf; -use std::time::SystemTime; impl AuthActivity { - /// ### read_bookmarks - /// - /// Read bookmarks from data file; Show popup if necessary - pub(super) fn read_bookmarks(&mut self) { - // Init bookmarks - if let Some(bookmark_file) = self.init_bookmarks() { - // Read - if self.context.is_some() { - match self - .context - .as_ref() - .unwrap() - .local - .open_file_read(bookmark_file.as_path()) - { - Ok(reader) => { - // Read bookmarks - let deserializer: BookmarkSerializer = BookmarkSerializer {}; - match deserializer.deserialize(Box::new(reader)) { - Ok(bookmarks) => self.bookmarks = Some(bookmarks), - Err(err) => { - self.input_mode = InputMode::Popup(PopupType::Alert( - Color::Yellow, - format!( - "Could not read bookmarks from \"{}\": {}", - bookmark_file.display(), - err - ), - )) - } - } - } - Err(err) => { - self.input_mode = InputMode::Popup(PopupType::Alert( - Color::Yellow, - format!( - "Could not read bookmarks from \"{}\": {}", - bookmark_file.display(), - err - ), - )) - } - } - } - } - } - /// ### del_bookmark /// /// Delete bookmark pub(super) fn del_bookmark(&mut self, idx: usize) { - if let Some(hosts) = self.bookmarks.as_mut() { + if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() { // Iterate over kyes let mut name: Option = None; - for (i, key) in hosts.bookmarks.keys().enumerate() { + for (i, key) in bookmarks_cli.iter_bookmarks().enumerate() { if i == idx { name = Some(key.clone()); break; } } if let Some(name) = name { - hosts.bookmarks.remove(name.as_str()); + bookmarks_cli.del_bookmark(&name); + // Write bookmarks + self.write_bookmarks(); } } } @@ -107,20 +60,20 @@ impl AuthActivity { /// /// Load selected bookmark (at index) to input fields pub(super) fn load_bookmark(&mut self, idx: usize) { - if let Some(hosts) = self.bookmarks.as_mut() { + if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() { // Iterate over bookmarks - for (i, bookmark) in hosts.bookmarks.values().enumerate() { + for (i, key) in bookmarks_cli.iter_bookmarks().enumerate() { if i == idx { - // Load parameters - self.address = bookmark.address.clone(); - self.port = bookmark.port.to_string(); - self.protocol = match bookmark.protocol.as_str().to_uppercase().as_str() { - "FTP" => FileTransferProtocol::Ftp(false), - "FTPS" => FileTransferProtocol::Ftp(true), - "SCP" => FileTransferProtocol::Scp, - _ => FileTransferProtocol::Sftp, // Default to SFTP - }; - self.username = bookmark.username.clone(); + if let Some(bookmark) = bookmarks_cli.get_bookmark(&key) { + // Load parameters + self.address = bookmark.0; + self.port = bookmark.1.to_string(); + self.protocol = bookmark.2; + self.username = bookmark.3; + if let Some(password) = bookmark.4 { + self.password = password; + } + } // Break break; } @@ -132,29 +85,61 @@ impl AuthActivity { /// /// Save current input fields as a bookmark pub(super) fn save_bookmark(&mut self, name: String) { - if let Ok(host) = self.make_user_host() { - if let Some(hosts) = self.bookmarks.as_mut() { - hosts.bookmarks.insert(name, host); - // Write bookmarks - self.write_bookmarks(); + // Check port + let port: u16 = match self.port.parse::() { + Ok(val) => { + if val > 65535 { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Red, + String::from("Specified port must be in range 0-65535"), + )); + return; + } + val as u16 } + Err(_) => { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Red, + String::from("Specified port is not a number"), + )); + return; + } + }; + if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() { + // Check if password must be saved + let password: Option = match self.choice_opt { + DialogYesNoOption::Yes => Some(self.password.clone()), + DialogYesNoOption::No => None, + }; + bookmarks_cli.add_bookmark( + name, + self.address.clone(), + port, + self.protocol, + self.username.clone(), + password, + ); + // Save bookmarks + self.write_bookmarks(); } } /// ### del_recent /// /// Delete recent pub(super) fn del_recent(&mut self, idx: usize) { - if let Some(hosts) = self.bookmarks.as_mut() { + if let Some(client) = self.bookmarks_client.as_mut() { // Iterate over kyes let mut name: Option = None; - for (i, key) in hosts.recents.keys().enumerate() { + for (i, key) in client.iter_recents().enumerate() { if i == idx { name = Some(key.clone()); break; } } if let Some(name) = name { - hosts.recents.remove(name.as_str()); + client.del_recent(&name); + // Save bookmarks + self.write_bookmarks(); } } } @@ -163,22 +148,19 @@ impl AuthActivity { /// /// Load selected recent (at index) to input fields pub(super) fn load_recent(&mut self, idx: usize) { - if let Some(hosts) = self.bookmarks.as_mut() { + if let Some(client) = self.bookmarks_client.as_ref() { // Iterate over bookmarks - for (i, bookmark) in hosts.recents.values().enumerate() { + for (i, key) in client.iter_recents().enumerate() { if i == idx { - // Load parameters - self.address = bookmark.address.clone(); - self.port = bookmark.port.to_string(); - self.protocol = match bookmark.protocol.as_str().to_uppercase().as_str() { - "FTP" => FileTransferProtocol::Ftp(false), - "FTPS" => FileTransferProtocol::Ftp(true), - "SCP" => FileTransferProtocol::Scp, - _ => FileTransferProtocol::Sftp, // Default to SFTP - }; - self.username = bookmark.username.clone(); - // Break - break; + if let Some(bookmark) = client.get_recent(key) { + // Load parameters + self.address = bookmark.0; + self.port = bookmark.1.to_string(); + self.protocol = bookmark.2; + self.username = bookmark.3; + // Break + break; + } } } } @@ -188,46 +170,6 @@ impl AuthActivity { /// /// Save current input fields as a "recent" pub(super) fn save_recent(&mut self) { - if let Ok(host) = self.make_user_host() { - if let Some(hosts) = self.bookmarks.as_mut() { - // Check if duplicated - for recent_host in hosts.recents.values() { - if *recent_host == host { - // Don't save duplicates - return; - } - } - // If hosts size is bigger than 16; pop last - if hosts.recents.len() >= 16 { - let mut keys: Vec = Vec::with_capacity(hosts.recents.len()); - for key in hosts.recents.keys() { - keys.push(key.clone()); - } - // Sort keys; NOTE: most recent is the last element - keys.sort(); - // Delete keys starting from the last one - for key in keys.iter() { - let _ = hosts.recents.remove(key); - // If length is < 16; break - if hosts.recents.len() < 16 { - break; - } - } - } - // Create name - let name: String = time_to_str(SystemTime::now(), "ISO%Y%m%dT%H%M%S"); - // Save host to recents - hosts.recents.insert(name, host); - // Write bookmarks - self.write_bookmarks(); - } - } - } - - /// ### make_user_host - /// - /// Make user host from current input fields - fn make_user_host(&mut self) -> Result { // Check port let port: u16 = match self.port.parse::() { Ok(val) => { @@ -236,7 +178,7 @@ impl AuthActivity { Color::Red, String::from("Specified port must be in range 0-65535"), )); - return Err(()); + return; } val as u16 } @@ -245,156 +187,72 @@ impl AuthActivity { Color::Red, String::from("Specified port is not a number"), )); - return Err(()); + return; } }; - Ok(Bookmark { - address: self.address.clone(), - port, - protocol: match self.protocol { - FileTransferProtocol::Ftp(secure) => match secure { - true => String::from("FTPS"), - false => String::from("FTP"), - }, - FileTransferProtocol::Scp => String::from("SCP"), - FileTransferProtocol::Sftp => String::from("SFTP"), - }, - username: self.username.clone(), - }) + if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() { + bookmarks_cli.add_recent( + self.address.clone(), + port, + self.protocol, + self.username.clone(), + ); + // Save bookmarks + self.write_bookmarks(); + } } /// ### write_bookmarks /// /// Write bookmarks to file fn write_bookmarks(&mut self) { - if self.bookmarks.is_some() && self.context.is_some() { - // Open file for write - if let Some(bookmarks_file) = self.init_bookmarks() { - match self - .context - .as_ref() - .unwrap() - .local - .open_file_write(bookmarks_file.as_path()) - { - Ok(writer) => { - let serializer: BookmarkSerializer = BookmarkSerializer {}; - if let Err(err) = serializer - .serialize(Box::new(writer), &self.bookmarks.as_ref().unwrap()) - { - self.input_mode = InputMode::Popup(PopupType::Alert( - Color::Yellow, - format!( - "Could not write default bookmarks at \"{}\": {}", - bookmarks_file.display(), - err - ), - )); - } - } - Err(err) => { - self.input_mode = InputMode::Popup(PopupType::Alert( - Color::Yellow, - format!( - "Could not write default bookmarks at \"{}\": {}", - bookmarks_file.display(), - err - ), - )) - } - } + if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() { + if let Err(err) = bookmarks_cli.write_bookmarks() { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Red, + format!("Could not write bookmarks: {}", err), + )); } } } - /// ### init_bookmarks + /// ### init_bookmarks_client /// - /// Initialize bookmarks directory - /// Returns bookmark path - fn init_bookmarks(&mut self) -> Option { - // Get file - lazy_static! { - static ref CONF_DIR: Option = dirs::config_dir(); - } - if CONF_DIR.is_some() { - // Get path of bookmarks - let mut p: PathBuf = CONF_DIR.as_ref().unwrap().clone(); - // Append termscp dir - p.push("termscp/"); - // Mkdir if doesn't exist - if self.context.is_some() { - if let Err(err) = self - .context - .as_mut() - .unwrap() - .local - .mkdir_ex(p.as_path(), true) - { - // Show popup - self.input_mode = InputMode::Popup(PopupType::Alert( - Color::Yellow, - format!( - "Could not create configuration directory at \"{}\": {}", - p.display(), - err - ), - )); - // Return None - return None; - } - } - // Append bookmarks.toml - p.push("bookmarks.toml"); - // If bookmarks.toml doesn't exist, initializae it - if self.context.is_some() - && !self - .context - .as_ref() - .unwrap() - .local - .file_exists(p.as_path()) - { - // Write file - let default_hosts: UserHosts = Default::default(); - match self - .context - .as_ref() - .unwrap() - .local - .open_file_write(p.as_path()) - { - Ok(writer) => { - let serializer: BookmarkSerializer = BookmarkSerializer {}; - // Serialize and write - if let Err(err) = serializer.serialize(Box::new(writer), &default_hosts) { + /// Initialize bookmarks client + pub(super) fn init_bookmarks_client(&mut self) { + // Get config dir + match environment::init_config_dir() { + Ok(path) => { + // If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system. + if let Some(path) = path { + // Prepare paths + let mut bookmarks_file: PathBuf = path.clone(); + bookmarks_file.push("bookmarks.toml"); + let mut key_file: PathBuf = path.clone(); + key_file.push(".bookmarks.key"); // key file is hidden + // Initialize client + match BookmarksClient::new(bookmarks_file.as_path(), key_file.as_path()) { + Ok(cli) => self.bookmarks_client = Some(cli), + Err(err) => { self.input_mode = InputMode::Popup(PopupType::Alert( - Color::Yellow, + Color::Red, format!( - "Could not write default bookmarks at \"{}\": {}", - p.display(), + "Could not initialize bookmarks (at \"{}\", \"{}\"): {}", + bookmarks_file.display(), + key_file.display(), err ), - )); - return None; + )) } } - Err(err) => { - self.input_mode = InputMode::Popup(PopupType::Alert( - Color::Yellow, - format!( - "Could not write default bookmarks at \"{}\": {}", - p.display(), - err - ), - )); - return None; - } } } - // return path - Some(p) - } else { - None + Err(err) => { + self.input_mode = InputMode::Popup(PopupType::Alert( + Color::Red, + format!("Could not initialize configuration directory: {}", err), + )) + } } } } diff --git a/src/ui/activities/auth_activity/input.rs b/src/ui/activities/auth_activity/input.rs index 3bcc7c5..a1dba71 100644 --- a/src/ui/activities/auth_activity/input.rs +++ b/src/ui/activities/auth_activity/input.rs @@ -234,14 +234,14 @@ impl AuthActivity { // Move bookmarks index up if self.bookmarks_idx > 0 { self.bookmarks_idx -= 1; - } else if let Some(hosts) = &self.bookmarks { + } else if let Some(bookmarks_cli) = &self.bookmarks_client { // Put to last index (wrap) - self.bookmarks_idx = hosts.bookmarks.len() - 1; + self.bookmarks_idx = bookmarks_cli.iter_bookmarks().count() - 1; } } KeyCode::Down => { - if let Some(hosts) = &self.bookmarks { - let size: usize = hosts.bookmarks.len(); + if let Some(bookmarks_cli) = &self.bookmarks_client { + let size: usize = bookmarks_cli.iter_bookmarks().count(); // Check if can move down if self.bookmarks_idx + 1 >= size { // Move bookmarks index down @@ -315,14 +315,14 @@ impl AuthActivity { // Move bookmarks index up if self.recents_idx > 0 { self.recents_idx -= 1; - } else if let Some(hosts) = &self.bookmarks { + } else if let Some(bookmarks_cli) = &self.bookmarks_client { // Put to last index (wrap) - self.recents_idx = hosts.recents.len() - 1; + self.recents_idx = bookmarks_cli.iter_recents().count() - 1; } } KeyCode::Down => { - if let Some(hosts) = &self.bookmarks { - let size: usize = hosts.recents.len(); + if let Some(bookmarks_cli) = &self.bookmarks_client { + let size: usize = bookmarks_cli.iter_recents().count(); // Check if can move down if self.recents_idx + 1 >= size { // Move bookmarks index down diff --git a/src/ui/activities/auth_activity/layout.rs b/src/ui/activities/auth_activity/layout.rs index 02be048..33775c1 100644 --- a/src/ui/activities/auth_activity/layout.rs +++ b/src/ui/activities/auth_activity/layout.rs @@ -27,8 +27,6 @@ use super::{ AuthActivity, Context, DialogYesNoOption, FileTransferProtocol, InputField, InputForm, InputMode, PopupType, }; - -use crate::bookmarks::Bookmark; use crate::utils::align_text_center; use tui::{ @@ -273,21 +271,26 @@ impl AuthActivity { /// /// Draw local explorer list pub(super) fn draw_bookmarks_tab(&self) -> Option { - self.bookmarks.as_ref()?; + self.bookmarks_client.as_ref()?; let hosts: Vec = self - .bookmarks + .bookmarks_client .as_ref() .unwrap() - .bookmarks - .iter() - .map(|(key, entry): (&String, &Bookmark)| { + .iter_bookmarks() + .map(|key: &String| { + let entry: (String, u16, FileTransferProtocol, String, _) = self + .bookmarks_client + .as_ref() + .unwrap() + .get_bookmark(key) + .unwrap(); ListItem::new(Span::from(format!( "{} ({}://{}@{}:{})", key, - entry.protocol.to_lowercase(), - entry.username, - entry.address, - entry.port + AuthActivity::protocol_to_str(entry.2), + entry.3, + entry.0, + entry.1 ))) }) .collect(); @@ -316,20 +319,25 @@ impl AuthActivity { /// /// Draw local explorer list pub(super) fn draw_recents_tab(&self) -> Option { - self.bookmarks.as_ref()?; + self.bookmarks_client.as_ref()?; let hosts: Vec = self - .bookmarks + .bookmarks_client .as_ref() .unwrap() - .recents - .values() - .map(|entry: &Bookmark| { + .iter_recents() + .map(|key: &String| { + let entry: (String, u16, FileTransferProtocol, String, _) = self + .bookmarks_client + .as_ref() + .unwrap() + .get_bookmark(key) + .unwrap(); ListItem::new(Span::from(format!( "{}://{}@{}:{}", - entry.protocol.to_lowercase(), - entry.username, - entry.address, - entry.port + AuthActivity::protocol_to_str(entry.2), + entry.3, + entry.0, + entry.1 ))) }) .collect(); @@ -538,4 +546,18 @@ impl AuthActivity { ) .start_corner(Corner::TopLeft) } + + /// ### protocol_to_str + /// + /// Convert protocol to str for layouts + fn protocol_to_str(proto: FileTransferProtocol) -> &'static str { + match proto { + FileTransferProtocol::Ftp(secure) => match secure { + true => "ftps", + false => "ftp", + }, + FileTransferProtocol::Scp => "scp", + FileTransferProtocol::Sftp => "sftp", + } + } } diff --git a/src/ui/activities/auth_activity/mod.rs b/src/ui/activities/auth_activity/mod.rs index fe66be7..ef08e1a 100644 --- a/src/ui/activities/auth_activity/mod.rs +++ b/src/ui/activities/auth_activity/mod.rs @@ -36,8 +36,8 @@ extern crate unicode_width; // locals use super::{Activity, Context}; -use crate::bookmarks::UserHosts; use crate::filetransfer::FileTransferProtocol; +use crate::system::bookmarks_client::BookmarksClient; // Includes use crossterm::event::Event as InputEvent; @@ -111,7 +111,7 @@ pub struct AuthActivity { pub submit: bool, // becomes true after user has submitted fields pub quit: bool, // Becomes true if user has pressed esc context: Option, - bookmarks: Option, + bookmarks_client: Option, selected_field: InputField, // Selected field in AuthCredentials Form input_mode: InputMode, input_form: InputForm, @@ -143,7 +143,7 @@ impl AuthActivity { submit: false, quit: false, context: None, - bookmarks: None, + bookmarks_client: None, selected_field: InputField::Address, input_mode: InputMode::Form, input_form: InputForm::AuthCredentials, @@ -171,9 +171,9 @@ impl Activity for AuthActivity { // Put raw mode on enabled let _ = enable_raw_mode(); self.input_mode = InputMode::Form; - // Read bookmarks - if self.bookmarks.is_none() { - self.read_bookmarks(); + // Init bookmarks client + if self.bookmarks_client.is_none() { + self.init_bookmarks_client(); } } From 65c541ff2afdbdff51414ae5965c796170c2145b Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 16:32:50 +0100 Subject: [PATCH 09/15] bookmarks client OK --- src/system/bookmarks_client.rs | 196 ++++++++++++++++++++++++++++++++- 1 file changed, 191 insertions(+), 5 deletions(-) diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index f22426c..ac9f05d 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -44,7 +44,7 @@ use std::time::SystemTime; /// /// BookmarksClient provides a layer between the host system and the bookmarks module pub struct BookmarksClient { - pub hosts: UserHosts, + hosts: UserHosts, bookmarks_file: PathBuf, key: String, } @@ -69,7 +69,7 @@ impl BookmarksClient { Err(err) => return Err(err), }, }; - let client: BookmarksClient = BookmarksClient { + let mut client: BookmarksClient = BookmarksClient { hosts: default_hosts, bookmarks_file: PathBuf::from(bookmarks_file), key, @@ -79,11 +79,23 @@ impl BookmarksClient { if let Err(err) = client.write_bookmarks() { return Err(err); } + } else { + // Load bookmarks from file + if let Err(err) = client.read_bookmarks() { + return Err(err); + } } // Load key Ok(client) } + /// ### iter_bookmarks + /// + /// Iterate over bookmarks keys + pub fn iter_bookmarks(&self) -> Box + '_> { + Box::new(self.hosts.bookmarks.keys()) + } + /// ### get_bookmark /// /// Get bookmark associated to key @@ -102,7 +114,7 @@ impl BookmarksClient { _ => FileTransferProtocol::Sftp, }, entry.username.clone(), - match entry.password { + match &entry.password { // Decrypted password if Some; if decryption fails return None Some(pwd) => match self.decrypt_str(pwd.as_str()) { Ok(decrypted_pwd) => Some(decrypted_pwd), @@ -130,6 +142,19 @@ impl BookmarksClient { self.hosts.bookmarks.insert(name, host); } + /// ### del_bookmark + /// + /// Delete entry from bookmarks + pub fn del_bookmark(&mut self, name: &String) { + let _ = self.hosts.bookmarks.remove(name); + } + /// ### iter_recents + /// + /// Iterate over recents keys + pub fn iter_recents(&self) -> Box + '_> { + Box::new(self.hosts.recents.keys()) + } + /// ### get_recent /// /// Get recent associated to key @@ -189,6 +214,13 @@ impl BookmarksClient { self.hosts.recents.insert(name, host); } + /// ### del_recent + /// + /// Delete entry from recents + pub fn del_recent(&mut self, name: &String) { + let _ = self.hosts.recents.remove(name); + } + /// ### write_bookmarks /// /// Write bookmarks to file @@ -211,6 +243,33 @@ impl BookmarksClient { } } + /// ### read_bookmarks + /// + /// Read bookmarks from file + fn read_bookmarks(&mut self) -> Result<(), SerializerError> { + // Open bookmarks file for read + match OpenOptions::new() + .read(true) + .open(self.bookmarks_file.as_path()) + { + Ok(reader) => { + // Deserialize + let deserializer: BookmarkSerializer = BookmarkSerializer {}; + match deserializer.deserialize(Box::new(reader)) { + Ok(hosts) => { + self.hosts = hosts; + Ok(()) + } + Err(err) => Err(err), + } + } + Err(err) => Err(SerializerError::new_ex( + SerializerErrorKind::IoError, + err.to_string(), + )), + } + } + /// ### generate_key /// /// Generate a new AES key and write it to key file @@ -285,8 +344,13 @@ impl BookmarksClient { match OpenOptions::new().read(true).open(key_file) { Ok(mut file) => { let mut key: String = String::with_capacity(256); - file.read_to_string(&mut key); - Ok(key) + match file.read_to_string(&mut key) { + Ok(_) => Ok(key), + Err(err) => Err(SerializerError::new_ex( + SerializerErrorKind::IoError, + err.to_string(), + )), + } } Err(err) => Err(SerializerError::new_ex( SerializerErrorKind::IoError, @@ -317,3 +381,125 @@ impl BookmarksClient { } } } + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_system_bookmarks_new() { + let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); + // Initialize a new bookmarks client + let client: BookmarksClient = BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + // Verify client + assert_eq!(client.hosts.bookmarks.len(), 0); + assert_eq!(client.hosts.recents.len(), 0); + assert!(client.key.len() > 0); + assert_eq!(client.bookmarks_file, cfg_path); + } + + #[test] + fn test_system_bookmarks_new_err() { + assert!(BookmarksClient::new(Path::new("/tmp/oifoif/omar"), Path::new("/tmp/efnnu/omar")).is_err()); + } + + #[test] + fn test_system_bookmarks_new_from_existing() { + let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); + // Initialize a new bookmarks client + let mut client: BookmarksClient = BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + // Add some bookmarks + client.add_bookmark(String::from("raspberry"), String::from("192.168.1.31"), 22, FileTransferProtocol::Sftp, String::from("pi"), Some(String::from("mypassword"))); + client.add_recent(String::from("192.168.1.31"), 22, FileTransferProtocol::Sftp, String::from("pi")); + let recent_key: String = String::from(client.iter_recents().next().unwrap()); + assert!(client.write_bookmarks().is_ok()); + let key: String = client.key.clone(); + // Re-initialize a client + let client: BookmarksClient = BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + // Verify it loaded parameters correctly + assert_eq!(client.key, key); + let bookmark: (String, u16, FileTransferProtocol, String, Option) = client.get_bookmark(&String::from("raspberry")).unwrap(); + assert_eq!(bookmark.0, String::from("192.168.1.31")); + assert_eq!(bookmark.1, 22); + assert_eq!(bookmark.2, FileTransferProtocol::Sftp); + assert_eq!(bookmark.3, String::from("pi")); + assert_eq!(*bookmark.4.as_ref().unwrap(), String::from("mypassword")); + let bookmark: (String, u16, FileTransferProtocol, String) = client.get_recent(&recent_key).unwrap(); + assert_eq!(bookmark.0, String::from("192.168.1.31")); + assert_eq!(bookmark.1, 22); + assert_eq!(bookmark.2, FileTransferProtocol::Sftp); + assert_eq!(bookmark.3, String::from("pi")); + } + + #[test] + fn test_system_bookmarks_manipulate_bookmarks() { + let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); + // Initialize a new bookmarks client + let mut client: BookmarksClient = BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + // Add bookmark + client.add_bookmark(String::from("raspberry"), String::from("192.168.1.31"), 22, FileTransferProtocol::Sftp, String::from("pi"), Some(String::from("mypassword"))); + // Get bookmark + let bookmark: (String, u16, FileTransferProtocol, String, Option) = client.get_bookmark(&String::from("raspberry")).unwrap(); + assert_eq!(bookmark.0, String::from("192.168.1.31")); + assert_eq!(bookmark.1, 22); + assert_eq!(bookmark.2, FileTransferProtocol::Sftp); + assert_eq!(bookmark.3, String::from("pi")); + assert_eq!(*bookmark.4.as_ref().unwrap(), String::from("mypassword")); + // Write bookmarks + assert!(client.write_bookmarks().is_ok()); + // Delete bookmark + client.del_bookmark(&String::from("raspberry")); + // Get unexisting bookmark + assert!(client.get_bookmark(&String::from("raspberry")).is_none()); + // Write bookmarks + assert!(client.write_bookmarks().is_ok()); + } + + #[test] + fn test_system_bookmarks_manipulate_recents() { + let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); + // Initialize a new bookmarks client + let mut client: BookmarksClient = BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + // Add bookmark + client.add_recent(String::from("192.168.1.31"), 22, FileTransferProtocol::Sftp, String::from("pi")); + let key: String = String::from(client.iter_recents().next().unwrap()); + // Get bookmark + let bookmark: (String, u16, FileTransferProtocol, String) = client.get_recent(&key).unwrap(); + assert_eq!(bookmark.0, String::from("192.168.1.31")); + assert_eq!(bookmark.1, 22); + assert_eq!(bookmark.2, FileTransferProtocol::Sftp); + assert_eq!(bookmark.3, String::from("pi")); + // Write bookmarks + assert!(client.write_bookmarks().is_ok()); + // Delete bookmark + client.del_recent(&key); + // Get unexisting bookmark + assert!(client.get_bookmark(&key).is_none()); + // Write bookmarks + assert!(client.write_bookmarks().is_ok()); + } + + /// ### get_paths + /// + /// Get paths for configuration and key for bookmarks + fn get_paths(dir: &Path) -> (PathBuf, PathBuf) { + let mut k: PathBuf = PathBuf::from(dir); + let mut c: PathBuf = k.clone(); + k.push("bookmarks.key"); + c.push("bookmarks.toml"); + (c, k) + } + + /// ### create_tmp_dir + /// + /// Create temporary directory + fn create_tmp_dir() -> tempfile::TempDir { + tempfile::TempDir::new().ok().unwrap() + } + +} From 562a1b3ae8c098a5201cc033f982ca026e088ff2 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 16:35:11 +0100 Subject: [PATCH 10/15] Clippy optimizations --- src/activity_manager.rs | 2 +- src/system/bookmarks_client.rs | 77 ++++++++++++++----- src/ui/activities/auth_activity/bookmarks.rs | 2 +- .../activities/filetransfer_activity/mod.rs | 2 +- 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/activity_manager.rs b/src/activity_manager.rs index 68133c3..de81411 100644 --- a/src/activity_manager.rs +++ b/src/activity_manager.rs @@ -160,7 +160,7 @@ impl ActivityManager { 0 => None, _ => Some(activity.password.clone()), }, - protocol: activity.protocol.clone(), + protocol: activity.protocol, }); break; } diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index ac9f05d..0f48c35 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -101,7 +101,7 @@ impl BookmarksClient { /// Get bookmark associated to key pub fn get_bookmark( &self, - key: &String, + key: &str, ) -> Option<(String, u16, FileTransferProtocol, String, Option)> { let entry: &Bookmark = self.hosts.bookmarks.get(key)?; Some(( @@ -145,7 +145,7 @@ impl BookmarksClient { /// ### del_bookmark /// /// Delete entry from bookmarks - pub fn del_bookmark(&mut self, name: &String) { + pub fn del_bookmark(&mut self, name: &str) { let _ = self.hosts.bookmarks.remove(name); } /// ### iter_recents @@ -158,7 +158,7 @@ impl BookmarksClient { /// ### get_recent /// /// Get recent associated to key - pub fn get_recent(&self, key: &String) -> Option<(String, u16, FileTransferProtocol, String)> { + pub fn get_recent(&self, key: &str) -> Option<(String, u16, FileTransferProtocol, String)> { // NOTE: password is not decrypted; recents will never have password let entry: &Bookmark = self.hosts.recents.get(key)?; Some(( @@ -217,7 +217,7 @@ impl BookmarksClient { /// ### del_recent /// /// Delete entry from recents - pub fn del_recent(&mut self, name: &String) { + pub fn del_recent(&mut self, name: &str) { let _ = self.hosts.recents.remove(name); } @@ -392,7 +392,8 @@ mod tests { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client - let client: BookmarksClient = BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + let client: BookmarksClient = + BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); // Verify client assert_eq!(client.hosts.bookmarks.len(), 0); assert_eq!(client.hosts.recents.len(), 0); @@ -402,7 +403,10 @@ mod tests { #[test] fn test_system_bookmarks_new_err() { - assert!(BookmarksClient::new(Path::new("/tmp/oifoif/omar"), Path::new("/tmp/efnnu/omar")).is_err()); + assert!( + BookmarksClient::new(Path::new("/tmp/oifoif/omar"), Path::new("/tmp/efnnu/omar")) + .is_err() + ); } #[test] @@ -410,24 +414,40 @@ mod tests { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client - let mut client: BookmarksClient = BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + let mut client: BookmarksClient = + BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); // Add some bookmarks - client.add_bookmark(String::from("raspberry"), String::from("192.168.1.31"), 22, FileTransferProtocol::Sftp, String::from("pi"), Some(String::from("mypassword"))); - client.add_recent(String::from("192.168.1.31"), 22, FileTransferProtocol::Sftp, String::from("pi")); + client.add_bookmark( + String::from("raspberry"), + String::from("192.168.1.31"), + 22, + FileTransferProtocol::Sftp, + String::from("pi"), + Some(String::from("mypassword")), + ); + client.add_recent( + String::from("192.168.1.31"), + 22, + FileTransferProtocol::Sftp, + String::from("pi"), + ); let recent_key: String = String::from(client.iter_recents().next().unwrap()); assert!(client.write_bookmarks().is_ok()); let key: String = client.key.clone(); // Re-initialize a client - let client: BookmarksClient = BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + let client: BookmarksClient = + BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); // Verify it loaded parameters correctly assert_eq!(client.key, key); - let bookmark: (String, u16, FileTransferProtocol, String, Option) = client.get_bookmark(&String::from("raspberry")).unwrap(); + let bookmark: (String, u16, FileTransferProtocol, String, Option) = + client.get_bookmark(&String::from("raspberry")).unwrap(); assert_eq!(bookmark.0, String::from("192.168.1.31")); assert_eq!(bookmark.1, 22); assert_eq!(bookmark.2, FileTransferProtocol::Sftp); assert_eq!(bookmark.3, String::from("pi")); assert_eq!(*bookmark.4.as_ref().unwrap(), String::from("mypassword")); - let bookmark: (String, u16, FileTransferProtocol, String) = client.get_recent(&recent_key).unwrap(); + let bookmark: (String, u16, FileTransferProtocol, String) = + client.get_recent(&recent_key).unwrap(); assert_eq!(bookmark.0, String::from("192.168.1.31")); assert_eq!(bookmark.1, 22); assert_eq!(bookmark.2, FileTransferProtocol::Sftp); @@ -439,11 +459,20 @@ mod tests { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client - let mut client: BookmarksClient = BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + let mut client: BookmarksClient = + BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); // Add bookmark - client.add_bookmark(String::from("raspberry"), String::from("192.168.1.31"), 22, FileTransferProtocol::Sftp, String::from("pi"), Some(String::from("mypassword"))); + client.add_bookmark( + String::from("raspberry"), + String::from("192.168.1.31"), + 22, + FileTransferProtocol::Sftp, + String::from("pi"), + Some(String::from("mypassword")), + ); // Get bookmark - let bookmark: (String, u16, FileTransferProtocol, String, Option) = client.get_bookmark(&String::from("raspberry")).unwrap(); + let bookmark: (String, u16, FileTransferProtocol, String, Option) = + client.get_bookmark(&String::from("raspberry")).unwrap(); assert_eq!(bookmark.0, String::from("192.168.1.31")); assert_eq!(bookmark.1, 22); assert_eq!(bookmark.2, FileTransferProtocol::Sftp); @@ -464,12 +493,19 @@ mod tests { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); // Initialize a new bookmarks client - let mut client: BookmarksClient = BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + let mut client: BookmarksClient = + BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); // Add bookmark - client.add_recent(String::from("192.168.1.31"), 22, FileTransferProtocol::Sftp, String::from("pi")); + client.add_recent( + String::from("192.168.1.31"), + 22, + FileTransferProtocol::Sftp, + String::from("pi"), + ); let key: String = String::from(client.iter_recents().next().unwrap()); // Get bookmark - let bookmark: (String, u16, FileTransferProtocol, String) = client.get_recent(&key).unwrap(); + let bookmark: (String, u16, FileTransferProtocol, String) = + client.get_recent(&key).unwrap(); assert_eq!(bookmark.0, String::from("192.168.1.31")); assert_eq!(bookmark.1, 22); assert_eq!(bookmark.2, FileTransferProtocol::Sftp); @@ -485,7 +521,7 @@ mod tests { } /// ### get_paths - /// + /// /// Get paths for configuration and key for bookmarks fn get_paths(dir: &Path) -> (PathBuf, PathBuf) { let mut k: PathBuf = PathBuf::from(dir); @@ -496,10 +532,9 @@ mod tests { } /// ### create_tmp_dir - /// + /// /// Create temporary directory fn create_tmp_dir() -> tempfile::TempDir { tempfile::TempDir::new().ok().unwrap() } - } diff --git a/src/ui/activities/auth_activity/bookmarks.rs b/src/ui/activities/auth_activity/bookmarks.rs index eddc7cd..e473c81 100644 --- a/src/ui/activities/auth_activity/bookmarks.rs +++ b/src/ui/activities/auth_activity/bookmarks.rs @@ -228,7 +228,7 @@ impl AuthActivity { // Prepare paths let mut bookmarks_file: PathBuf = path.clone(); bookmarks_file.push("bookmarks.toml"); - let mut key_file: PathBuf = path.clone(); + let mut key_file: PathBuf = path; key_file.push(".bookmarks.key"); // key file is hidden // Initialize client match BookmarksClient::new(bookmarks_file.as_path(), key_file.as_path()) { diff --git a/src/ui/activities/filetransfer_activity/mod.rs b/src/ui/activities/filetransfer_activity/mod.rs index ce7f5c3..dba2add 100644 --- a/src/ui/activities/filetransfer_activity/mod.rs +++ b/src/ui/activities/filetransfer_activity/mod.rs @@ -269,7 +269,7 @@ impl FileTransferActivity { /// /// Instantiates a new FileTransferActivity pub fn new(params: FileTransferParams) -> FileTransferActivity { - let protocol: FileTransferProtocol = params.protocol.clone(); + let protocol: FileTransferProtocol = params.protocol; FileTransferActivity { disconnected: false, quit: false, From 344bf8604f7644e6220f34176eb1c39209aa0570 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 16:51:21 +0100 Subject: [PATCH 11/15] Fixed crash in auth_activity --- src/ui/activities/auth_activity/layout.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/activities/auth_activity/layout.rs b/src/ui/activities/auth_activity/layout.rs index 33775c1..e13c703 100644 --- a/src/ui/activities/auth_activity/layout.rs +++ b/src/ui/activities/auth_activity/layout.rs @@ -326,11 +326,11 @@ impl AuthActivity { .unwrap() .iter_recents() .map(|key: &String| { - let entry: (String, u16, FileTransferProtocol, String, _) = self + let entry: (String, u16, FileTransferProtocol, String) = self .bookmarks_client .as_ref() .unwrap() - .get_bookmark(key) + .get_recent(key) .unwrap(); ListItem::new(Span::from(format!( "{}://{}@{}:{}", From 335bfc84604aeb373a55ca1629445395de09611e Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 17:01:11 +0100 Subject: [PATCH 12/15] Added 'save password' tab to auth activity when saving bookmarks --- src/ui/activities/auth_activity/callbacks.rs | 4 +- src/ui/activities/auth_activity/input.rs | 26 +++++++--- src/ui/activities/auth_activity/layout.rs | 52 ++++++++++++++++---- src/ui/activities/auth_activity/mod.rs | 2 +- 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/ui/activities/auth_activity/callbacks.rs b/src/ui/activities/auth_activity/callbacks.rs index ca859e1..5fe802d 100644 --- a/src/ui/activities/auth_activity/callbacks.rs +++ b/src/ui/activities/auth_activity/callbacks.rs @@ -53,6 +53,8 @@ impl AuthActivity { /// /// Callback used to save bookmark with name pub(super) fn callback_save_bookmark(&mut self, input: String) { - self.save_bookmark(input); + if !input.is_empty() { + self.save_bookmark(input); + } } } diff --git a/src/ui/activities/auth_activity/input.rs b/src/ui/activities/auth_activity/input.rs index a1dba71..4ced128 100644 --- a/src/ui/activities/auth_activity/input.rs +++ b/src/ui/activities/auth_activity/input.rs @@ -160,8 +160,10 @@ impl AuthActivity { self.input_mode = InputMode::Popup(PopupType::Help); } 'S' | 's' => { + // Default choice option to no + self.choice_opt = DialogYesNoOption::No; // Save bookmark as... - self.input_mode = InputMode::Popup(PopupType::Input( + self.input_mode = InputMode::Popup(PopupType::SaveBookmark( String::from("Save bookmark as..."), AuthActivity::callback_save_bookmark, )); @@ -282,8 +284,10 @@ impl AuthActivity { self.input_mode = InputMode::Popup(PopupType::Help); } 'S' | 's' => { + // Default choice option to no + self.choice_opt = DialogYesNoOption::No; // Save bookmark as... - self.input_mode = InputMode::Popup(PopupType::Input( + self.input_mode = InputMode::Popup(PopupType::SaveBookmark( String::from("Save bookmark as..."), AuthActivity::callback_save_bookmark, )); @@ -363,8 +367,10 @@ impl AuthActivity { self.input_mode = InputMode::Popup(PopupType::Help); } 'S' | 's' => { + // Default choice option to no + self.choice_opt = DialogYesNoOption::No; // Save bookmark as... - self.input_mode = InputMode::Popup(PopupType::Input( + self.input_mode = InputMode::Popup(PopupType::SaveBookmark( String::from("Save bookmark as..."), AuthActivity::callback_save_bookmark, )); @@ -383,7 +389,7 @@ impl AuthActivity { match ptype { PopupType::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev), PopupType::Help => self.handle_input_event_mode_popup_help(ev), - PopupType::Input(_, cb) => self.handle_input_event_mode_popup_input(ev, cb), + PopupType::SaveBookmark(_, cb) => self.handle_input_event_mode_popup_save_bookmark(ev, cb), PopupType::YesNo(_, yes_cb, no_cb) => { self.handle_input_event_mode_popup_yesno(ev, yes_cb, no_cb) } @@ -418,10 +424,10 @@ impl AuthActivity { } } - /// ### handle_input_event_mode_popup_input + /// ### handle_input_event_mode_popup_save_bookmark /// - /// Input event handler for input popup - pub(super) fn handle_input_event_mode_popup_input( + /// Input event handler for SaveBookmark popup + pub(super) fn handle_input_event_mode_popup_save_bookmark( &mut self, ev: &InputEvent, cb: OnInputSubmitCallback, @@ -435,6 +441,8 @@ impl AuthActivity { self.input_txt.clear(); // Set mode back to form self.input_mode = InputMode::Form; + // Reset choice option to yes + self.choice_opt = DialogYesNoOption::Yes; } KeyCode::Enter => { // Submit @@ -445,7 +453,11 @@ impl AuthActivity { self.input_mode = InputMode::Form; // Call cb cb(self, input_text); + // Reset choice option to yes + self.choice_opt = DialogYesNoOption::Yes; } + KeyCode::Left => self.choice_opt = DialogYesNoOption::Yes, // Move yes/no with arrows + KeyCode::Right => self.choice_opt = DialogYesNoOption::No, // Move yes/no with arrows KeyCode::Char(ch) => self.input_txt.push(ch), KeyCode::Backspace => { let _ = self.input_txt.pop(); diff --git a/src/ui/activities/auth_activity/layout.rs b/src/ui/activities/auth_activity/layout.rs index e13c703..ed32e53 100644 --- a/src/ui/activities/auth_activity/layout.rs +++ b/src/ui/activities/auth_activity/layout.rs @@ -126,7 +126,7 @@ impl AuthActivity { let (width, height): (u16, u16) = match popup { PopupType::Alert(_, _) => (50, 10), PopupType::Help => (50, 70), - PopupType::Input(_, _) => (40, 10), + PopupType::SaveBookmark(_, _) => (40, 20), PopupType::YesNo(_, _, _) => (30, 10), }; let popup_area: Rect = self.draw_popup_area(f.size(), width, height); @@ -137,12 +137,27 @@ impl AuthActivity { popup_area, ), PopupType::Help => f.render_widget(self.draw_popup_help(), popup_area), - PopupType::Input(txt, _) => { - f.render_widget(self.draw_popup_input(txt.clone()), popup_area); + PopupType::SaveBookmark(txt, _) => { + let popup_chunks = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints( + [ + Constraint::Length(3), // Input form + Constraint::Length(2), // Yes/No + ] + .as_ref(), + ) + .split(popup_area); + let (input, yes_no): (Paragraph, Tabs) = + self.draw_popup_save_bookmark(txt.clone()); + // Render parts + f.render_widget(input, popup_chunks[0]); + f.render_widget(yes_no, popup_chunks[1]); // Set cursor f.set_cursor( - popup_area.x + self.input_txt.width() as u16 + 1, - popup_area.y + 1, + popup_chunks[0].x + self.input_txt.width() as u16 + 1, + popup_chunks[0].y + 1, ) } PopupType::YesNo(txt, _, _) => { @@ -414,10 +429,29 @@ impl AuthActivity { /// ### draw_popup_input /// /// Draw input popup - pub(super) fn draw_popup_input(&self, text: String) -> Paragraph { - Paragraph::new(self.input_txt.as_ref()) - .style(Style::default().fg(Color::White)) - .block(Block::default().borders(Borders::ALL).title(text)) + pub(super) fn draw_popup_save_bookmark(&self, text: String) -> (Paragraph, Tabs) { + let input: Paragraph = Paragraph::new(self.input_txt.as_ref()) + .style(Style::default().fg(Color::LightYellow)) + .block(Block::default().borders(Borders::ALL).title(text)); + let choices: Vec = vec![Spans::from("Yes"), Spans::from("No")]; + let index: usize = match self.choice_opt { + DialogYesNoOption::Yes => 0, + DialogYesNoOption::No => 1, + }; + let tabs: Tabs = Tabs::new(choices) + .block( + Block::default() + .borders(Borders::ALL) + .title("Save password?"), + ) + .select(index) + .style(Style::default()) + .highlight_style( + Style::default() + .add_modifier(Modifier::BOLD) + .fg(Color::LightRed), + ); + (input, tabs) } /// ### draw_popup_yesno diff --git a/src/ui/activities/auth_activity/mod.rs b/src/ui/activities/auth_activity/mod.rs index ef08e1a..08ed4bc 100644 --- a/src/ui/activities/auth_activity/mod.rs +++ b/src/ui/activities/auth_activity/mod.rs @@ -76,7 +76,7 @@ enum DialogYesNoOption { enum PopupType { Alert(Color, String), // Show a message displaying text with the provided color Help, // Help page - Input(String, OnInputSubmitCallback), // Input description; Callback for submit + SaveBookmark(String, OnInputSubmitCallback), // Input description; Callback for submit YesNo(String, DialogCallback, DialogCallback), // Yes, no callback } From 38e015efe4f2a020bb9d3a67f9ab751df5700279 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 20:34:36 +0100 Subject: [PATCH 13/15] removed args from SaveBookmark --- src/ui/activities/auth_activity/input.rs | 27 ++++++----------------- src/ui/activities/auth_activity/layout.rs | 19 +++++++++------- src/ui/activities/auth_activity/mod.rs | 3 +-- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/src/ui/activities/auth_activity/input.rs b/src/ui/activities/auth_activity/input.rs index 4ced128..64d587c 100644 --- a/src/ui/activities/auth_activity/input.rs +++ b/src/ui/activities/auth_activity/input.rs @@ -25,7 +25,7 @@ use super::{ AuthActivity, DialogCallback, DialogYesNoOption, FileTransferProtocol, InputEvent, InputField, - InputForm, InputMode, OnInputSubmitCallback, PopupType, + InputForm, InputMode, PopupType, }; use crossterm::event::{KeyCode, KeyModifiers}; @@ -163,10 +163,7 @@ impl AuthActivity { // Default choice option to no self.choice_opt = DialogYesNoOption::No; // Save bookmark as... - self.input_mode = InputMode::Popup(PopupType::SaveBookmark( - String::from("Save bookmark as..."), - AuthActivity::callback_save_bookmark, - )); + self.input_mode = InputMode::Popup(PopupType::SaveBookmark); } _ => { /* Nothing to do */ } } @@ -287,10 +284,7 @@ impl AuthActivity { // Default choice option to no self.choice_opt = DialogYesNoOption::No; // Save bookmark as... - self.input_mode = InputMode::Popup(PopupType::SaveBookmark( - String::from("Save bookmark as..."), - AuthActivity::callback_save_bookmark, - )); + self.input_mode = InputMode::Popup(PopupType::SaveBookmark); } _ => { /* Nothing to do */ } }, @@ -370,10 +364,7 @@ impl AuthActivity { // Default choice option to no self.choice_opt = DialogYesNoOption::No; // Save bookmark as... - self.input_mode = InputMode::Popup(PopupType::SaveBookmark( - String::from("Save bookmark as..."), - AuthActivity::callback_save_bookmark, - )); + self.input_mode = InputMode::Popup(PopupType::SaveBookmark); } _ => { /* Nothing to do */ } }, @@ -389,7 +380,7 @@ impl AuthActivity { match ptype { PopupType::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev), PopupType::Help => self.handle_input_event_mode_popup_help(ev), - PopupType::SaveBookmark(_, cb) => self.handle_input_event_mode_popup_save_bookmark(ev, cb), + PopupType::SaveBookmark => self.handle_input_event_mode_popup_save_bookmark(ev), PopupType::YesNo(_, yes_cb, no_cb) => { self.handle_input_event_mode_popup_yesno(ev, yes_cb, no_cb) } @@ -427,11 +418,7 @@ impl AuthActivity { /// ### handle_input_event_mode_popup_save_bookmark /// /// Input event handler for SaveBookmark popup - pub(super) fn handle_input_event_mode_popup_save_bookmark( - &mut self, - ev: &InputEvent, - cb: OnInputSubmitCallback, - ) { + pub(super) fn handle_input_event_mode_popup_save_bookmark(&mut self, ev: &InputEvent) { // If enter, close popup, otherwise push chars to input if let InputEvent::Key(key) = ev { match key.code { @@ -452,7 +439,7 @@ impl AuthActivity { // Set mode back to form BEFORE CALLBACKS!!! Callback can then overwrite this, clever uh? self.input_mode = InputMode::Form; // Call cb - cb(self, input_text); + self.callback_save_bookmark(input_text); // Reset choice option to yes self.choice_opt = DialogYesNoOption::Yes; } diff --git a/src/ui/activities/auth_activity/layout.rs b/src/ui/activities/auth_activity/layout.rs index ed32e53..365aa79 100644 --- a/src/ui/activities/auth_activity/layout.rs +++ b/src/ui/activities/auth_activity/layout.rs @@ -126,7 +126,7 @@ impl AuthActivity { let (width, height): (u16, u16) = match popup { PopupType::Alert(_, _) => (50, 10), PopupType::Help => (50, 70), - PopupType::SaveBookmark(_, _) => (40, 20), + PopupType::SaveBookmark => (40, 20), PopupType::YesNo(_, _, _) => (30, 10), }; let popup_area: Rect = self.draw_popup_area(f.size(), width, height); @@ -137,7 +137,7 @@ impl AuthActivity { popup_area, ), PopupType::Help => f.render_widget(self.draw_popup_help(), popup_area), - PopupType::SaveBookmark(txt, _) => { + PopupType::SaveBookmark => { let popup_chunks = Layout::default() .direction(Direction::Vertical) .margin(1) @@ -149,8 +149,7 @@ impl AuthActivity { .as_ref(), ) .split(popup_area); - let (input, yes_no): (Paragraph, Tabs) = - self.draw_popup_save_bookmark(txt.clone()); + let (input, yes_no): (Paragraph, Tabs) = self.draw_popup_save_bookmark(); // Render parts f.render_widget(input, popup_chunks[0]); f.render_widget(yes_no, popup_chunks[1]); @@ -429,10 +428,14 @@ impl AuthActivity { /// ### draw_popup_input /// /// Draw input popup - pub(super) fn draw_popup_save_bookmark(&self, text: String) -> (Paragraph, Tabs) { + pub(super) fn draw_popup_save_bookmark(&self) -> (Paragraph, Tabs) { let input: Paragraph = Paragraph::new(self.input_txt.as_ref()) - .style(Style::default().fg(Color::LightYellow)) - .block(Block::default().borders(Borders::ALL).title(text)); + .style(Style::default().fg(Color::White)) + .block( + Block::default() + .borders(Borders::TOP | Borders::RIGHT | Borders::LEFT) + .title("Save bookmark as..."), + ); let choices: Vec = vec![Spans::from("Yes"), Spans::from("No")]; let index: usize = match self.choice_opt { DialogYesNoOption::Yes => 0, @@ -441,7 +444,7 @@ impl AuthActivity { let tabs: Tabs = Tabs::new(choices) .block( Block::default() - .borders(Borders::ALL) + .borders(Borders::BOTTOM | Borders::RIGHT | Borders::LEFT) .title("Save password?"), ) .select(index) diff --git a/src/ui/activities/auth_activity/mod.rs b/src/ui/activities/auth_activity/mod.rs index 08ed4bc..c958799 100644 --- a/src/ui/activities/auth_activity/mod.rs +++ b/src/ui/activities/auth_activity/mod.rs @@ -46,7 +46,6 @@ use tui::style::Color; // Types type DialogCallback = fn(&mut AuthActivity); -type OnInputSubmitCallback = fn(&mut AuthActivity, String); /// ### InputField /// @@ -76,7 +75,7 @@ enum DialogYesNoOption { enum PopupType { Alert(Color, String), // Show a message displaying text with the provided color Help, // Help page - SaveBookmark(String, OnInputSubmitCallback), // Input description; Callback for submit + SaveBookmark, YesNo(String, DialogCallback, DialogCallback), // Yes, no callback } From dd6e2be75d1cef6d81f3a10310cf2930bb3ee39f Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 20:39:35 +0100 Subject: [PATCH 14/15] Panic if bookmark name is empty --- src/system/bookmarks_client.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index 0f48c35..cd3a66b 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -137,6 +137,9 @@ impl BookmarksClient { username: String, password: Option, ) { + if name.is_empty() { + panic!("Bookmark name can't be empty"); + } // Make bookmark let host: Bookmark = self.make_bookmark(addr, port, protocol, username, password); self.hosts.bookmarks.insert(name, host); @@ -520,6 +523,25 @@ mod tests { assert!(client.write_bookmarks().is_ok()); } + #[test] + #[should_panic] + fn test_system_bookmarks_add_bookmark_empty() { + let tmp_dir: tempfile::TempDir = create_tmp_dir(); + let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); + // Initialize a new bookmarks client + let mut client: BookmarksClient = + BookmarksClient::new(cfg_path.as_path(), key_path.as_path()).unwrap(); + // Add bookmark + client.add_bookmark( + String::from(""), + String::from("192.168.1.31"), + 22, + FileTransferProtocol::Sftp, + String::from("pi"), + Some(String::from("mypassword")), + ); + } + /// ### get_paths /// /// Get paths for configuration and key for bookmarks From 3cd9fc407e1cb34ee31a95bfd257e043998d890b Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Wed, 16 Dec 2020 20:41:42 +0100 Subject: [PATCH 15/15] Bookmarks layout OK --- src/ui/activities/auth_activity/layout.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ui/activities/auth_activity/layout.rs b/src/ui/activities/auth_activity/layout.rs index 365aa79..94e2e43 100644 --- a/src/ui/activities/auth_activity/layout.rs +++ b/src/ui/activities/auth_activity/layout.rs @@ -126,7 +126,7 @@ impl AuthActivity { let (width, height): (u16, u16) = match popup { PopupType::Alert(_, _) => (50, 10), PopupType::Help => (50, 70), - PopupType::SaveBookmark => (40, 20), + PopupType::SaveBookmark => (20, 20), PopupType::YesNo(_, _, _) => (30, 10), }; let popup_area: Rect = self.draw_popup_area(f.size(), width, height); @@ -140,7 +140,6 @@ impl AuthActivity { PopupType::SaveBookmark => { let popup_chunks = Layout::default() .direction(Direction::Vertical) - .margin(1) .constraints( [ Constraint::Length(3), // Input form