Skip to main content

shadow_crypt_shell/encryption/
validation.rs

1use std::path::PathBuf;
2
3use shadow_crypt_core::profile::SecurityProfile;
4
5use crate::{
6    encryption::{
7        cli::EncryptionCliArgs,
8        file::{EncryptionInputFile, InputKind},
9    },
10    errors::{WorkflowError, WorkflowResult},
11};
12
13pub struct ValidEncryptionArgs {
14    pub files: Vec<EncryptionInputFile>,
15    pub security_profile: SecurityProfile,
16    pub output_dir: Option<PathBuf>,
17    pub password_file: Option<PathBuf>,
18    pub quiet: bool,
19    pub delete: bool,
20}
21
22pub fn validate_input(input: EncryptionCliArgs) -> WorkflowResult<ValidEncryptionArgs> {
23    if input.input_files.is_empty() {
24        return Err(WorkflowError::UserInput(
25            "No input files provided".to_string(),
26        ));
27    }
28
29    let mut validated_files: Vec<EncryptionInputFile> = Vec::new();
30    for raw in &input.input_files {
31        validate_path(PathBuf::from(raw), &mut validated_files)?;
32    }
33
34    Ok(ValidEncryptionArgs {
35        files: validated_files,
36        security_profile: input.profile.into(),
37        output_dir: input.output_dir,
38        password_file: input.password_file,
39        quiet: input.quiet,
40        delete: input.delete,
41    })
42}
43
44fn validate_path(path: PathBuf, out: &mut Vec<EncryptionInputFile>) -> WorkflowResult<()> {
45    if !path.exists() {
46        return Err(WorkflowError::UserInput(format!(
47            "Input path does not exist: {}",
48            path.display()
49        )));
50    }
51
52    let name = path_name(&path)?;
53
54    if path.is_dir() {
55        out.push(EncryptionInputFile {
56            path,
57            filename: name,
58            size: 0,
59            kind: InputKind::Directory,
60        });
61        return Ok(());
62    }
63
64    if !path.is_file() {
65        return Err(WorkflowError::UserInput(format!(
66            "Input path is not a file or directory: {}",
67            path.display()
68        )));
69    }
70
71    let size: u64 = path
72        .metadata()
73        .map_err(|_| {
74            WorkflowError::UserInput(format!(
75                "Unable to read metadata for file: {}",
76                path.display()
77            ))
78        })?
79        .len();
80
81    out.push(EncryptionInputFile {
82        path,
83        filename: name,
84        size,
85        kind: InputKind::File,
86    });
87    Ok(())
88}
89
90fn path_name(path: &std::path::Path) -> WorkflowResult<String> {
91    Ok(path
92        .file_name()
93        .and_then(|n| n.to_str())
94        .ok_or_else(|| {
95            WorkflowError::UserInput(format!("Invalid filename for path: {}", path.display()))
96        })?
97        .to_string())
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use std::io::Write;
104    use tempfile::NamedTempFile;
105
106    #[test]
107    fn test_validate_input_no_files() {
108        let input = EncryptionCliArgs {
109            input_files: vec![],
110            ..Default::default()
111        };
112        let result = validate_input(input);
113        assert!(result.is_err());
114        if let Err(WorkflowError::UserInput(msg)) = result {
115            assert_eq!(msg, "No input files provided");
116        } else {
117            panic!("Expected UserInput error");
118        }
119    }
120
121    #[test]
122    fn test_validate_input_file_does_not_exist() {
123        let input = EncryptionCliArgs {
124            input_files: vec!["nonexistent_file.txt".to_string()],
125            ..Default::default()
126        };
127        let result = validate_input(input);
128        assert!(result.is_err());
129        if let Err(WorkflowError::UserInput(msg)) = result {
130            assert!(msg.contains("Input path does not exist"));
131        } else {
132            panic!("Expected UserInput error");
133        }
134    }
135
136    #[test]
137    fn test_validate_input_directory_becomes_archive_item() {
138        let temp_dir = tempfile::tempdir().unwrap();
139        let dir = temp_dir.path().join("photos");
140        std::fs::create_dir(&dir).unwrap();
141        std::fs::write(dir.join("a.txt"), b"a").unwrap();
142
143        let input = EncryptionCliArgs {
144            input_files: vec![dir.to_str().unwrap().to_string()],
145            ..Default::default()
146        };
147        let valid = validate_input(input).unwrap();
148        assert_eq!(valid.files.len(), 1);
149        assert_eq!(valid.files[0].kind, InputKind::Directory);
150        assert_eq!(valid.files[0].filename, "photos");
151    }
152
153    #[test]
154    fn test_validate_input_valid_file() {
155        let mut temp_file = NamedTempFile::new().unwrap();
156        let content = b"Hello, world!";
157        temp_file.write_all(content).unwrap();
158        let file_path = temp_file.path().to_path_buf();
159
160        let input = EncryptionCliArgs {
161            input_files: vec![file_path.to_str().unwrap().to_string()],
162            profile: crate::encryption::cli::CliProfile::Test,
163            ..Default::default()
164        };
165        let result = validate_input(input);
166        assert!(result.is_ok());
167        let valid_args = result.unwrap();
168        assert_eq!(valid_args.files.len(), 1);
169        assert_eq!(valid_args.security_profile, SecurityProfile::Test);
170        let file = &valid_args.files[0];
171        assert_eq!(file.path, file_path);
172        assert_eq!(
173            file.filename,
174            file_path.file_name().unwrap().to_str().unwrap()
175        );
176        assert_eq!(file.size, content.len() as u64);
177    }
178
179    #[test]
180    fn test_validate_input_multiple_files() {
181        let mut temp_file1 = NamedTempFile::new().unwrap();
182        temp_file1.write_all(b"File 1").unwrap();
183        let path1 = temp_file1.path().to_path_buf();
184
185        let mut temp_file2 = NamedTempFile::new().unwrap();
186        temp_file2.write_all(b"File 2 content").unwrap();
187        let path2 = temp_file2.path().to_path_buf();
188
189        let input = EncryptionCliArgs {
190            input_files: vec![
191                path1.to_str().unwrap().to_string(),
192                path2.to_str().unwrap().to_string(),
193            ],
194            ..Default::default()
195        };
196        let result = validate_input(input);
197        assert!(result.is_ok());
198        let valid_args = result.unwrap();
199        assert_eq!(valid_args.files.len(), 2);
200        assert_eq!(valid_args.security_profile, SecurityProfile::Standard);
201
202        // Check first file
203        let file1 = &valid_args.files[0];
204        assert_eq!(file1.path, path1);
205        assert_eq!(file1.filename, path1.file_name().unwrap().to_str().unwrap());
206        assert_eq!(file1.size, 6); // "File 1".len()
207
208        // Check second file
209        let file2 = &valid_args.files[1];
210        assert_eq!(file2.path, path2);
211        assert_eq!(file2.filename, path2.file_name().unwrap().to_str().unwrap());
212        assert_eq!(file2.size, 14); // "File 2 content".len()
213    }
214}