ftp transfer, brough parse_list_line as a method of the struct

This commit is contained in:
ChristianVisintin
2020-12-03 08:04:40 +01:00
parent 129c168989
commit 9b26b5b99d

View File

@@ -33,8 +33,8 @@ use crate::fs::{FsDirectory, FsEntry, FsFile};
use crate::utils::lstime_to_systime; use crate::utils::lstime_to_systime;
// Includes // Includes
use ftp::{FtpStream, FtpError};
use ftp::openssl::ssl::{SslContext, SslMethod}; use ftp::openssl::ssl::{SslContext, SslMethod};
use ftp::{FtpError, FtpStream};
use regex::Regex; use regex::Regex;
use std::io::{Read, Seek, Write}; use std::io::{Read, Seek, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -49,7 +49,6 @@ pub struct FtpFileTransfer {
} }
impl FtpFileTransfer { impl FtpFileTransfer {
/// ### new /// ### new
/// ///
/// Instantiates a new `FtpFileTransfer` /// Instantiates a new `FtpFileTransfer`
@@ -60,124 +59,23 @@ impl FtpFileTransfer {
} }
} }
} /// ### parse_list_line
impl FileTransfer for FtpFileTransfer {
/// ### connect
/// ///
/// Connect to the remote server /// Parse a line of LIST command output and instantiates an FsEntry from it
fn parse_list_line(&self, path: &Path, line: &str) -> Result<FsEntry, ()> {
fn connect(
&mut self,
address: String,
port: u16,
username: Option<String>,
password: Option<String>,
) -> Result<(), FileTransferError> {
// 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))),
};
// 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)))
}
}
// 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)))
}
}
// Set stream
self.stream = Some(stream);
// Return OK
Ok(())
}
/// ### disconnect
///
/// Disconnect from the remote server
fn disconnect(&mut self) -> Result<(), FileTransferError> {
match self.stream {
Some(stream) => match stream.quit() {
Ok(_) => Ok(()),
Err(err) => Err(FileTransferError::new_ex(FileTransferErrorType::ConnectionError, format!("{}", err)))
},
None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession))
}
}
/// ### is_connected
///
/// Indicates whether the client is connected to remote
fn is_connected(&self) -> bool {
match self.stream {
Some(_) => true,
None => false,
}
}
/// ### pwd
///
/// Print working directory
fn pwd(&self) -> Result<PathBuf, FileTransferError> {
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)))
},
None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession))
}
}
/// ### change_dir
///
/// Change working directory
fn change_dir(&mut self, dir: &Path) -> Result<PathBuf, FileTransferError> {
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))
}
}
/// ### list_dir
///
/// List directory entries
fn list_dir(&self, path: &Path) -> Result<Vec<FsEntry>, FileTransferError> {
// Prepare list regex // Prepare list regex
// NOTE: about this damn regex <https://stackoverflow.com/questions/32480890/is-there-a-regex-to-parse-the-values-from-an-ftp-directory-listing> // NOTE: about this damn regex <https://stackoverflow.com/questions/32480890/is-there-a-regex-to-parse-the-values-from-an-ftp-directory-listing>
lazy_static! { 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(); 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) => {
// Prepare result
let mut result: Vec<FsEntry> = Vec::with_capacity(entries.len());
// Iterate over entries
for entry in entries.iter() {
// Apply regex to result // Apply regex to result
if let Some(metadata) = LS_RE.captures(entry) { // String matches regex match LS_RE.captures(line) {
// String matches regex
Some(metadata) => {
// NOTE: metadata fmt: (regex, file_type, permissions, link_count, uid, gid, filesize, mtime, filename) // 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 // Expected 7 + 1 (8) values: + 1 cause regex is repeated at 0
if metadata.len() < 8 { if metadata.len() < 8 {
continue return Err(());
} }
// Collect metadata // Collect metadata
// Get if is directory and if is symlink // Get if is directory and if is symlink
@@ -185,11 +83,11 @@ impl FileTransfer for FtpFileTransfer {
"-" => (false, false), "-" => (false, false),
"l" => (false, true), "l" => (false, true),
"d" => (true, false), "d" => (true, false),
_ => continue, // Ignore special files _ => return Err(()), // Ignore special files
}; };
// Check string length (unix pex) // Check string length (unix pex)
if metadata.get(2).unwrap().as_str().len() < 9 { if metadata.get(2).unwrap().as_str().len() < 9 {
continue; return Err(());
} }
// Get unix pex // Get unix pex
let unix_pex: (u8, u8, u8) = { let unix_pex: (u8, u8, u8) = {
@@ -197,8 +95,10 @@ impl FileTransfer for FtpFileTransfer {
let mut count: u8 = 0; let mut count: u8 = 0;
for (i, c) in metadata.get(2).unwrap().as_str()[0..3].chars().enumerate() { for (i, c) in metadata.get(2).unwrap().as_str()[0..3].chars().enumerate() {
match c { match c {
'-' => {}, '-' => {}
_ => count = count + match i { _ => {
count = count
+ match i {
0 => 4, 0 => 4,
1 => 2, 1 => 2,
2 => 1, 2 => 1,
@@ -206,14 +106,17 @@ impl FileTransfer for FtpFileTransfer {
} }
} }
} }
}
count count
}; };
let group_pex: u8 = { let group_pex: u8 = {
let mut count: u8 = 0; let mut count: u8 = 0;
for (i, c) in metadata.get(2).unwrap().as_str()[3..6].chars().enumerate() { for (i, c) in metadata.get(2).unwrap().as_str()[3..6].chars().enumerate() {
match c { match c {
'-' => {}, '-' => {}
_ => count = count + match i { _ => {
count = count
+ match i {
0 => 4, 0 => 4,
1 => 2, 1 => 2,
2 => 1, 2 => 1,
@@ -221,14 +124,17 @@ impl FileTransfer for FtpFileTransfer {
} }
} }
} }
}
count count
}; };
let others_pex: u8 = { let others_pex: u8 = {
let mut count: u8 = 0; let mut count: u8 = 0;
for (i, c) in metadata.get(2).unwrap().as_str()[6..9].chars().enumerate() { for (i, c) in metadata.get(2).unwrap().as_str()[6..9].chars().enumerate() {
match c { match c {
'-' => {}, '-' => {}
_ => count = count + match i { _ => {
count = count
+ match i {
0 => 4, 0 => 4,
1 => 2, 1 => 2,
2 => 1, 2 => 1,
@@ -236,40 +142,45 @@ impl FileTransfer for FtpFileTransfer {
} }
} }
} }
}
count count
}; };
(owner_pex, group_pex, others_pex) (owner_pex, group_pex, others_pex)
}; };
// Parse mtime and convert to SystemTime // 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") { let mtime: SystemTime = match lstime_to_systime(
metadata.get(7).unwrap().as_str(),
"%b %d %Y",
"%b %d %H:%M",
) {
Ok(t) => t, Ok(t) => t,
Err(_) => continue Err(_) => return Err(()),
}; };
// Get uid // Get uid
let uid: Option<u32> = match metadata.get(4).unwrap().as_str().parse::<u32>() { let uid: Option<u32> = match metadata.get(4).unwrap().as_str().parse::<u32>() {
Ok(uid) => Some(uid), Ok(uid) => Some(uid),
Err(_) => None Err(_) => None,
}; };
// Get gid // Get gid
let gid: Option<u32> = match metadata.get(5).unwrap().as_str().parse::<u32>() { let gid: Option<u32> = match metadata.get(5).unwrap().as_str().parse::<u32>() {
Ok(gid) => Some(gid), Ok(gid) => Some(gid),
Err(_) => None Err(_) => None,
}; };
// Get filesize // Get filesize
let filesize: usize = match metadata.get(6).unwrap().as_str().parse::<usize>() { let filesize: usize = match metadata.get(6).unwrap().as_str().parse::<usize>() {
Ok(sz) => sz, Ok(sz) => sz,
Err(_) => continue Err(_) => return Err(()),
}; };
let file_name: String = String::from(metadata.get(8).unwrap().as_str()); let file_name: String = String::from(metadata.get(8).unwrap().as_str());
let mut abs_path: PathBuf = PathBuf::from(path); let mut abs_path: PathBuf = PathBuf::from(path);
let extension: Option<String> = match abs_path.as_path().extension() { let extension: Option<String> = match abs_path.as_path().extension() {
None => None, None => None,
Some(s) => Some(String::from(s.to_string_lossy())) Some(s) => Some(String::from(s.to_string_lossy())),
}; };
abs_path.push(file_name.as_str()); abs_path.push(file_name.as_str());
// Return // Return
// Push to entries // Push to entries
result.push(match is_dir { Ok(match is_dir {
true => FsEntry::Directory(FsDirectory { true => FsEntry::Directory(FsDirectory {
name: file_name, name: file_name,
abs_path: abs_path, abs_path: abs_path,
@@ -295,15 +206,161 @@ impl FileTransfer for FtpFileTransfer {
user: uid, user: uid,
group: gid, group: gid,
unix_pex: Some(unix_pex), unix_pex: Some(unix_pex),
}),
}) })
}) }
None => Err(()),
}
}
}
impl FileTransfer for FtpFileTransfer {
/// ### connect
///
/// Connect to the remote server
fn connect(
&mut self,
address: String,
port: u16,
username: Option<String>,
password: Option<String>,
) -> Result<(), FileTransferError> {
// 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),
))
}
};
// 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),
));
}
}
// 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),
));
}
}
// Set stream
self.stream = Some(stream);
// Return OK
Ok(())
}
/// ### disconnect
///
/// Disconnect from the remote server
fn disconnect(&mut self) -> Result<(), FileTransferError> {
match self.stream {
Some(stream) => match stream.quit() {
Ok(_) => Ok(()),
Err(err) => Err(FileTransferError::new_ex(
FileTransferErrorType::ConnectionError,
format!("{}", err),
)),
},
None => Err(FileTransferError::new(
FileTransferErrorType::UninitializedSession,
)),
}
}
/// ### is_connected
///
/// Indicates whether the client is connected to remote
fn is_connected(&self) -> bool {
match self.stream {
Some(_) => true,
None => false,
}
}
/// ### pwd
///
/// Print working directory
fn pwd(&self) -> Result<PathBuf, FileTransferError> {
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),
)),
},
None => Err(FileTransferError::new(
FileTransferErrorType::UninitializedSession,
)),
}
}
/// ### change_dir
///
/// Change working directory
fn change_dir(&mut self, dir: &Path) -> Result<PathBuf, FileTransferError> {
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,
)),
}
}
/// ### list_dir
///
/// List directory entries
fn list_dir(&self, path: &Path) -> Result<Vec<FsEntry>, FileTransferError> {
match self.stream {
Some(stream) => match stream.list(Some(&path.as_os_str().to_string_lossy())) {
Ok(entries) => {
// Prepare result
let mut result: Vec<FsEntry> = Vec::with_capacity(entries.len());
// Iterate over entries
for entry in entries.iter() {
if let Ok(file) = self.parse_list_line(path, entry) {
result.push(file);
} }
} }
Ok(result) Ok(result)
} }
Err(err) => Err(FileTransferError::new_ex(FileTransferErrorType::DirStatFailed, format!("{}", err))), Err(err) => Err(FileTransferError::new_ex(
} FileTransferErrorType::DirStatFailed,
None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) format!("{}", err),
)),
},
None => Err(FileTransferError::new(
FileTransferErrorType::UninitializedSession,
)),
} }
} }
@@ -312,8 +369,10 @@ impl FileTransfer for FtpFileTransfer {
/// Make directory /// Make directory
fn mkdir(&self, dir: &Path) -> Result<(), FileTransferError> { fn mkdir(&self, dir: &Path) -> Result<(), FileTransferError> {
match self.stream { match self.stream {
Some(stream) => {}, Some(stream) => {}
None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) None => Err(FileTransferError::new(
FileTransferErrorType::UninitializedSession,
)),
} }
} }
@@ -322,8 +381,10 @@ impl FileTransfer for FtpFileTransfer {
/// Remove a file or a directory /// Remove a file or a directory
fn remove(&self, file: &FsEntry) -> Result<(), FileTransferError> { fn remove(&self, file: &FsEntry) -> Result<(), FileTransferError> {
match self.stream { match self.stream {
Some(stream) => {}, Some(stream) => {}
None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) None => Err(FileTransferError::new(
FileTransferErrorType::UninitializedSession,
)),
} }
} }
@@ -332,8 +393,10 @@ impl FileTransfer for FtpFileTransfer {
/// Rename file or a directory /// Rename file or a directory
fn rename(&self, file: &FsEntry, dst: &Path) -> Result<(), FileTransferError> { fn rename(&self, file: &FsEntry, dst: &Path) -> Result<(), FileTransferError> {
match self.stream { match self.stream {
Some(stream) => {}, Some(stream) => {}
None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) None => Err(FileTransferError::new(
FileTransferErrorType::UninitializedSession,
)),
} }
} }
@@ -342,8 +405,12 @@ impl FileTransfer for FtpFileTransfer {
/// Stat file and return FsEntry /// Stat file and return FsEntry
fn stat(&self, path: &Path) -> Result<FsEntry, FileTransferError> { fn stat(&self, path: &Path) -> Result<FsEntry, FileTransferError> {
match self.stream { match self.stream {
Some(stream) => Err(FileTransferError::new(FileTransferErrorType::UnsupportedFeature)), Some(stream) => Err(FileTransferError::new(
None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) FileTransferErrorType::UnsupportedFeature,
)),
None => Err(FileTransferError::new(
FileTransferErrorType::UninitializedSession,
)),
} }
} }
@@ -355,8 +422,10 @@ impl FileTransfer for FtpFileTransfer {
/// Returns file and its size /// Returns file and its size
fn send_file(&self, file_name: &Path) -> Result<Box<dyn Write>, FileTransferError> { fn send_file(&self, file_name: &Path) -> Result<Box<dyn Write>, FileTransferError> {
match self.stream { match self.stream {
Some(stream) => {}, Some(stream) => {}
None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) None => Err(FileTransferError::new(
FileTransferErrorType::UninitializedSession,
)),
} }
} }
@@ -366,9 +435,10 @@ impl FileTransfer for FtpFileTransfer {
/// Returns file and its size /// Returns file and its size
fn recv_file(&self, file_name: &Path) -> Result<(Box<dyn Read>, usize), FileTransferError> { fn recv_file(&self, file_name: &Path) -> Result<(Box<dyn Read>, usize), FileTransferError> {
match self.stream { match self.stream {
Some(stream) => {}, Some(stream) => {}
None => Err(FileTransferError::new(FileTransferErrorType::UninitializedSession)) None => Err(FileTransferError::new(
FileTransferErrorType::UninitializedSession,
)),
} }
} }
} }