#[derive(Debug, Clone, Default)]
pub struct FileTransferOptions {
hold_timestamp: bool,
sync_mode: bool,
compress: bool,
mode_sync: bool,
debug_dir: bool,
cwd: Option<String>,
}
impl FileTransferOptions {
pub fn new() -> Self {
Self::default()
}
pub fn hold_timestamp(mut self, enable: bool) -> Self {
self.hold_timestamp = enable;
self
}
pub fn sync_mode(mut self, enable: bool) -> Self {
self.sync_mode = enable;
self
}
pub fn compress(mut self, enable: bool) -> Self {
self.compress = enable;
self
}
pub fn mode_sync(mut self, enable: bool) -> Self {
self.mode_sync = enable;
self
}
pub fn debug_dir(mut self, enable: bool) -> Self {
self.debug_dir = enable;
self
}
pub fn cwd(mut self, cwd: impl Into<String>) -> Self {
self.cwd = Some(cwd.into());
self
}
pub(crate) fn validate(&self) -> crate::error::Result<()> {
if let Some(cwd) = &self.cwd {
crate::app::validate_cwd_value("-cwd", cwd)?;
}
Ok(())
}
pub(crate) fn to_flags(&self) -> String {
let mut flags: Vec<String> = Vec::new();
if self.hold_timestamp {
flags.push("-a".to_string());
}
if self.sync_mode {
flags.push("-sync".to_string());
}
if self.compress {
flags.push("-z".to_string());
}
if self.mode_sync {
flags.push("-m".to_string());
}
if self.debug_dir {
flags.push("-b".to_string());
}
if let Some(cwd) = &self.cwd {
flags.push("-cwd".to_string());
flags.push(crate::app::render_cwd_value(cwd));
}
flags.join(" ")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileTransferDirection {
Send,
Recv,
}
pub(crate) fn validate_path(path: &str) -> bool {
!path.is_empty() && !path.contains('\0')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_options_flags() {
let opts = FileTransferOptions::new()
.hold_timestamp(true)
.compress(true);
assert_eq!(opts.to_flags(), "-a -z");
let opts = FileTransferOptions::new().sync_mode(true).mode_sync(true);
assert_eq!(opts.to_flags(), "-sync -m");
}
#[test]
fn test_file_options_render_debug_dir_and_cwd() {
let opts = FileTransferOptions::new()
.debug_dir(true)
.cwd("/data/local/tmp");
assert_eq!(opts.to_flags(), "-b -cwd /data/local/tmp");
let opts = FileTransferOptions::new().cwd("/data/local/tmp with space");
assert_eq!(opts.to_flags(), "-cwd \"/data/local/tmp with space\"");
}
#[test]
fn test_reject_unsafe_cwd() {
for value in ["bad\"path", "bad\npath", "bad\rpath", "bad\0path"] {
assert!(FileTransferOptions::new().cwd(value).validate().is_err());
}
}
#[test]
fn test_validate_path() {
assert!(validate_path("/data/local/tmp/test.txt"));
assert!(validate_path("test.txt"));
assert!(!validate_path(""));
assert!(!validate_path("test\0file"));
}
}