From 9b26b5b99dfc2d4789e3cf402d4c32a4accb9b41 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Thu, 3 Dec 2020 08:04:40 +0100 Subject: [PATCH] ftp transfer, brough parse_list_line as a method of the struct --- src/filetransfer/ftp_transfer.rs | 402 ++++++++++++++++++------------- 1 file changed, 236 insertions(+), 166 deletions(-) diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 13d8d19..476db08 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -33,15 +33,15 @@ use crate::fs::{FsDirectory, FsEntry, FsFile}; use crate::utils::lstime_to_systime; // Includes -use ftp::{FtpStream, FtpError}; use ftp::openssl::ssl::{SslContext, SslMethod}; +use ftp::{FtpError, FtpStream}; use regex::Regex; use std::io::{Read, Seek, Write}; use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime}; /// ## FtpFileTransfer -/// +/// /// Ftp file transfer struct pub struct FtpFileTransfer { stream: Option, @@ -49,9 +49,8 @@ pub struct FtpFileTransfer { } impl FtpFileTransfer { - /// ### new - /// + /// /// Instantiates a new `FtpFileTransfer` pub fn new(ftps: bool) -> FtpFileTransfer { FtpFileTransfer { @@ -60,10 +59,162 @@ impl FtpFileTransfer { } } + /// ### parse_list_line + /// + /// Parse a line of LIST command output and instantiates an FsEntry from it + fn parse_list_line(&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(()), + }; + let file_name: String = String::from(metadata.get(8).unwrap().as_str()); + 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: None, + 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: None, + user: uid, + group: gid, + unix_pex: Some(unix_pex), + }), + }) + } + None => Err(()), + } + } } impl FileTransfer for FtpFileTransfer { - /// ### connect /// /// Connect to the remote server @@ -78,23 +229,37 @@ impl FileTransfer for FtpFileTransfer { // Get stream let mut stream: FtpStream = match FtpStream::connect(format!("{}:{}", address, port)) { Ok(stream) => stream, - Err(err) => return Err(FileTransferError::new_ex(FileTransferErrorType::ConnectionError, format!("{}", err))), + Err(err) => { + return Err(FileTransferError::new_ex( + FileTransferErrorType::ConnectionError, + format!("{}", err), + )) + } }; // If SSL, open secure session if self.ftps { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); let ctx = ctx.build(); if let Err(err) = stream.into_secure(ctx) { - return Err(FileTransferError::new_ex(FileTransferErrorType::SslError, format!("{}", err))) + return Err(FileTransferError::new_ex( + FileTransferErrorType::SslError, + format!("{}", err), + )); } } // If username / password... if let Some(username) = username { - if let Err(err) = stream.login(username.as_str(), match password { - Some(pwd) => pwd.as_ref(), - None => "" - }) { - return Err(FileTransferError::new_ex(FileTransferErrorType::AuthenticationFailed, format!("{}", err))) + if let Err(err) = stream.login( + username.as_str(), + match password { + Some(pwd) => pwd.as_ref(), + None => "", + }, + ) { + return Err(FileTransferError::new_ex( + FileTransferErrorType::AuthenticationFailed, + format!("{}", err), + )); } } // Set stream @@ -111,9 +276,14 @@ impl FileTransfer for FtpFileTransfer { match self.stream { Some(stream) => match stream.quit() { Ok(_) => Ok(()), - Err(err) => Err(FileTransferError::new_ex(FileTransferErrorType::ConnectionError, format!("{}", err))) + Err(err) => Err(FileTransferError::new_ex( + FileTransferErrorType::ConnectionError, + format!("{}", err), + )), }, - None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), } } @@ -135,9 +305,14 @@ impl FileTransfer for FtpFileTransfer { match self.stream { Some(stream) => match stream.pwd() { Ok(path) => Ok(PathBuf::from(path.as_str())), - Err(err) => Err(FileTransferError::new_ex(FileTransferErrorType::ConnectionError, format!("{}", err))) + Err(err) => Err(FileTransferError::new_ex( + FileTransferErrorType::ConnectionError, + format!("{}", err), + )), }, - None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), } } @@ -149,9 +324,14 @@ impl FileTransfer for FtpFileTransfer { match self.stream { Some(stream) => match stream.cwd(&dir.as_os_str().to_string_lossy()) { Ok(_) => Ok(PathBuf::from(dir)), - Err(err) => Err(FileTransferError::new_ex(FileTransferErrorType::ConnectionError, format!("{}", err))), - } - None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) + Err(err) => Err(FileTransferError::new_ex( + FileTransferErrorType::ConnectionError, + format!("{}", err), + )), + }, + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), } } @@ -160,11 +340,6 @@ impl FileTransfer for FtpFileTransfer { /// List directory entries fn list_dir(&self, path: &Path) -> Result, FileTransferError> { - // 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(); - } match self.stream { Some(stream) => match stream.list(Some(&path.as_os_str().to_string_lossy())) { Ok(entries) => { @@ -172,138 +347,20 @@ impl FileTransfer for FtpFileTransfer { let mut result: Vec = Vec::with_capacity(entries.len()); // Iterate over entries for entry in entries.iter() { - // Apply regex to result - if let Some(metadata) = LS_RE.captures(entry) { // String matches regex - // 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 { - continue - } - // 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), - _ => continue, // Ignore special files - }; - // Check string length (unix pex) - if metadata.get(2).unwrap().as_str().len() < 9 { - continue; - } - // 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(_) => continue - }; - // 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(_) => continue - }; - let file_name: String = String::from(metadata.get(8).unwrap().as_str()); - 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 - result.push(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: None, - 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: None, - user: uid, - group: gid, - unix_pex: Some(unix_pex), - }) - }) + if let Ok(file) = self.parse_list_line(path, entry) { + result.push(file); } } Ok(result) } - Err(err) => Err(FileTransferError::new_ex(FileTransferErrorType::DirStatFailed, format!("{}", err))), - } - None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) + Err(err) => Err(FileTransferError::new_ex( + FileTransferErrorType::DirStatFailed, + format!("{}", err), + )), + }, + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), } } @@ -312,8 +369,10 @@ impl FileTransfer for FtpFileTransfer { /// Make directory fn mkdir(&self, dir: &Path) -> Result<(), FileTransferError> { match self.stream { - Some(stream) => {}, - None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) + Some(stream) => {} + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), } } @@ -322,8 +381,10 @@ impl FileTransfer for FtpFileTransfer { /// Remove a file or a directory fn remove(&self, file: &FsEntry) -> Result<(), FileTransferError> { match self.stream { - Some(stream) => {}, - None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) + Some(stream) => {} + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), } } @@ -332,18 +393,24 @@ impl FileTransfer for FtpFileTransfer { /// Rename file or a directory fn rename(&self, file: &FsEntry, dst: &Path) -> Result<(), FileTransferError> { match self.stream { - Some(stream) => {}, - None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) + Some(stream) => {} + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), } } /// ### stat - /// + /// /// Stat file and return FsEntry fn stat(&self, path: &Path) -> Result { match self.stream { - Some(stream) => Err(FileTransferError::new(FileTransferErrorType::UnsupportedFeature)), - None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) + Some(stream) => Err(FileTransferError::new( + FileTransferErrorType::UnsupportedFeature, + )), + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), } } @@ -355,8 +422,10 @@ impl FileTransfer for FtpFileTransfer { /// Returns file and its size fn send_file(&self, file_name: &Path) -> Result, FileTransferError> { match self.stream { - Some(stream) => {}, - None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) + Some(stream) => {} + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), } } @@ -366,9 +435,10 @@ impl FileTransfer for FtpFileTransfer { /// Returns file and its size fn recv_file(&self, file_name: &Path) -> Result<(Box, usize), FileTransferError> { match self.stream { - Some(stream) => {}, - None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) + Some(stream) => {} + None => Err(FileTransferError::new( + FileTransferErrorType::UninitializedSession, + )), } } - }