diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs
new file mode 100644
index 0000000..bbfbb50
--- /dev/null
+++ b/src/filetransfer/scp_transfer.rs
@@ -0,0 +1,1031 @@
+//! ## SFTP_Transfer
+//!
+//! `sftp_transfer` is the module which provides the implementation for the SFTP file transfer
+
+/*
+*
+* 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 .
+*
+*/
+
+// Dependencies
+extern crate regex;
+extern crate ssh2;
+
+// Locals
+use super::{FileTransfer, FileTransferError, FileTransferErrorType};
+use crate::fs::{FsDirectory, FsEntry, FsFile};
+use crate::utils::lstime_to_systime;
+
+// Includes
+use regex::Regex;
+use ssh2::{Channel, Session};
+use std::io::{Read, Write};
+use std::net::TcpStream;
+use std::path::{Path, PathBuf};
+use std::time::SystemTime;
+
+/// ## ScpFileTransfer
+///
+/// SFTP file transfer structure
+pub struct ScpFileTransfer {
+ session: Option,
+ pty: Option,
+}
+
+impl ScpFileTransfer {
+ /// ### new
+ ///
+ /// Instantiates a new ScpFileTransfer
+ pub fn new() -> ScpFileTransfer {
+ ScpFileTransfer {
+ session: None,
+ pty: None,
+ }
+ }
+
+ /// ### parse_ls_output
+ ///
+ /// Parse a line of `ls -l` output and tokenize the output into a `FsEntry`
+ fn parse_ls_output(&self, path: &Path, line: &str) -> Result {
+ // Prepare list regex
+ // NOTE: about this damn regex
+ lazy_static! {
+ static ref LS_RE: Regex = Regex::new(r#"^([\-ld])([\-rwxs]{9})\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\w{3}\s+\d{1,2}\s+(?:\d{1,2}:\d{1,2}|\d{4}))\s+(.+)$"#).unwrap();
+ }
+ // Apply regex to result
+ match LS_RE.captures(line) {
+ // String matches regex
+ Some(metadata) => {
+ // NOTE: metadata fmt: (regex, file_type, permissions, link_count, uid, gid, filesize, mtime, filename)
+ // Expected 7 + 1 (8) values: + 1 cause regex is repeated at 0
+ if metadata.len() < 8 {
+ return Err(());
+ }
+ // Collect metadata
+ // Get if is directory and if is symlink
+ let (is_dir, is_symlink): (bool, bool) = match metadata.get(1).unwrap().as_str() {
+ "-" => (false, false),
+ "l" => (false, true),
+ "d" => (true, false),
+ _ => return Err(()), // Ignore special files
+ };
+ // Check string length (unix pex)
+ if metadata.get(2).unwrap().as_str().len() < 9 {
+ return Err(());
+ }
+ // Get unix pex
+ let unix_pex: (u8, u8, u8) = {
+ let owner_pex: u8 = {
+ let mut count: u8 = 0;
+ for (i, c) in metadata.get(2).unwrap().as_str()[0..3].chars().enumerate() {
+ match c {
+ '-' => {}
+ _ => {
+ count = count
+ + match i {
+ 0 => 4,
+ 1 => 2,
+ 2 => 1,
+ _ => 0,
+ }
+ }
+ }
+ }
+ count
+ };
+ let group_pex: u8 = {
+ let mut count: u8 = 0;
+ for (i, c) in metadata.get(2).unwrap().as_str()[3..6].chars().enumerate() {
+ match c {
+ '-' => {}
+ _ => {
+ count = count
+ + match i {
+ 0 => 4,
+ 1 => 2,
+ 2 => 1,
+ _ => 0,
+ }
+ }
+ }
+ }
+ count
+ };
+ let others_pex: u8 = {
+ let mut count: u8 = 0;
+ for (i, c) in metadata.get(2).unwrap().as_str()[6..9].chars().enumerate() {
+ match c {
+ '-' => {}
+ _ => {
+ count = count
+ + match i {
+ 0 => 4,
+ 1 => 2,
+ 2 => 1,
+ _ => 0,
+ }
+ }
+ }
+ }
+ count
+ };
+ (owner_pex, group_pex, others_pex)
+ };
+ // Parse mtime and convert to SystemTime
+ let mtime: SystemTime = match lstime_to_systime(
+ metadata.get(7).unwrap().as_str(),
+ "%b %d %Y",
+ "%b %d %H:%M",
+ ) {
+ Ok(t) => t,
+ Err(_) => return Err(()),
+ };
+ // Get uid
+ let uid: Option = match metadata.get(4).unwrap().as_str().parse::() {
+ Ok(uid) => Some(uid),
+ Err(_) => None,
+ };
+ // Get gid
+ let gid: Option = match metadata.get(5).unwrap().as_str().parse::() {
+ Ok(gid) => Some(gid),
+ Err(_) => None,
+ };
+ // Get filesize
+ let filesize: usize = match metadata.get(6).unwrap().as_str().parse::() {
+ Ok(sz) => sz,
+ Err(_) => return Err(()),
+ };
+ // Get link and name
+ let (file_name, symlink_path): (String, Option) = match is_symlink {
+ true => self.get_name_and_link(metadata.get(8).unwrap().as_str()),
+ false => (String::from(metadata.get(8).unwrap().as_str()), None),
+ };
+ let mut abs_path: PathBuf = PathBuf::from(path);
+ let extension: Option = match abs_path.as_path().extension() {
+ None => None,
+ Some(s) => Some(String::from(s.to_string_lossy())),
+ };
+ abs_path.push(file_name.as_str());
+ // Return
+ // Push to entries
+ Ok(match is_dir {
+ true => FsEntry::Directory(FsDirectory {
+ name: file_name,
+ abs_path: abs_path,
+ last_change_time: mtime,
+ last_access_time: mtime,
+ creation_time: mtime,
+ readonly: false,
+ symlink: symlink_path,
+ user: uid,
+ group: gid,
+ unix_pex: Some(unix_pex),
+ }),
+ false => FsEntry::File(FsFile {
+ name: file_name,
+ abs_path: abs_path,
+ last_change_time: mtime,
+ last_access_time: mtime,
+ creation_time: mtime,
+ size: filesize,
+ ftype: extension,
+ readonly: false,
+ symlink: symlink_path,
+ user: uid,
+ group: gid,
+ unix_pex: Some(unix_pex),
+ }),
+ })
+ }
+ None => Err(()),
+ }
+ }
+
+ /// ### get_name_and_link
+ ///
+ /// Returns from a `ls -l` command output file name token, the name of the file and the symbolic link (if there is any)
+ fn get_name_and_link(&self, token: &str) -> (String, Option) {
+ let tokens: Vec<&str> = token.split(" -> ").collect();
+ let filename: String = String::from(*tokens.get(0).unwrap());
+ let symlink: Option = match tokens.get(1) {
+ Some(s) => Some(PathBuf::from(s)),
+ None => None,
+ };
+ (filename, symlink)
+ }
+
+ /// ### perform_shell_cmd
+ ///
+ /// Perform a shell command and read the output from shell
+ /// This operation is, obviously, blocking.
+ fn perform_shell_cmd(&mut self, cmd: &str) -> Result {
+ match self.pty.as_mut() {
+ Some(pty) => {
+ // Send command
+ let command: String = match cmd.ends_with("\n") {
+ true => String::from(cmd),
+ false => format!("{}\n", cmd),
+ };
+ if let Err(err) = pty.write(command.as_str().as_bytes()) {
+ return Err(FileTransferError::new_ex(
+ FileTransferErrorType::BadAddress,
+ format!("{}", err),
+ ));
+ }
+ // Read
+ let mut output: String = String::new();
+ let mut buffer: [u8; 8192] = [0; 8192];
+ loop {
+ match pty.read(&mut buffer) {
+ Ok(bytes_read) => {
+ if bytes_read == 0 {
+ break;
+ }
+ output.push_str(std::str::from_utf8(&buffer[0..bytes_read]).unwrap());
+ }
+ Err(err) => {
+ return Err(FileTransferError::new_ex(
+ FileTransferErrorType::BadAddress,
+ format!("{}", err),
+ ))
+ }
+ }
+ }
+ Ok(output)
+ }
+ None => Err(FileTransferError::new(
+ FileTransferErrorType::UninitializedSession,
+ )),
+ }
+ }
+}
+
+impl FileTransfer for ScpFileTransfer {
+ /// ### connect
+ ///
+ /// Connect to the remote server
+ fn connect(
+ &mut self,
+ address: String,
+ port: u16,
+ username: Option,
+ password: Option,
+ ) -> Result