shadow_crypt_shell/encryption/
validation.rs1use 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 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); 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); }
214}