fencryption_lib/commands/
logic.rs1use std::{
4 fs::{self, OpenOptions},
5 io::{Read, Write},
6 path::{Path, PathBuf},
7};
8
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 commands::{Command, ErrorBuilder, Result},
13 crypto::{self, Crypto},
14 metadata,
15};
16
17pub enum OutputDecPath {
18 Direct(PathBuf),
19 Parent(PathBuf),
20}
21
22#[derive(Serialize, Deserialize, Debug)]
23pub struct PathMetadata(PathBuf);
24
25impl PathMetadata {
26 pub fn new<P>(path: P) -> Self
27 where
28 P: AsRef<Path>,
29 {
30 PathMetadata(path.as_ref().to_owned())
31 }
32
33 pub fn path(&self) -> PathBuf {
34 self.0.to_owned()
35 }
36}
37
38pub fn checks<P>(input_paths: P, output_path: &Option<PathBuf>) -> Result<()>
39where
40 P: AsRef<Vec<PathBuf>>,
41{
42 if input_paths.as_ref().len() == 0 {
43 return Err(ErrorBuilder::new()
44 .message("Please provide at least one path")
45 .build());
46 }
47
48 if input_paths.as_ref().iter().any(|p| !p.exists()) {
49 return Err(ErrorBuilder::new()
50 .message("I can't work with files that don't exist")
51 .build());
52 }
53
54 if output_path.as_ref().is_some() && input_paths.as_ref().len() != 1 {
55 return Err(ErrorBuilder::new()
56 .message("Only one input path can be provided when setting an output path")
57 .build());
58 }
59
60 Ok(())
61}
62
63pub fn change_file_name<P, F>(path: P, callback: F) -> PathBuf
64where
65 P: AsRef<Path>,
66 F: FnOnce(&str) -> String,
67{
68 let mut path = path.as_ref().to_owned();
69 path.set_file_name(
70 [callback(
71 path.file_name()
72 .unwrap_or_default()
73 .to_str()
74 .unwrap_or_default(),
75 )]
76 .concat(),
77 );
78 path
79}
80
81pub fn get_output_paths(
82 paths: &Vec<PathBuf>,
83 output_path: &Option<PathBuf>,
84 command: Command,
85) -> Vec<PathBuf> {
86 paths
87 .iter()
88 .map(|p| match output_path {
89 Some(p) => p.to_owned(),
90 None => change_file_name(p, |s| match command {
91 Command::EncryptFile => [s, ".enc"].concat(),
92 Command::DecryptFile => {
93 if s.ends_with(".enc") {
94 s.replace(".enc", ".dec")
95 } else {
96 [s, ".dec"].concat()
97 }
98 } }),
100 })
101 .collect::<Vec<PathBuf>>()
102}
103
104pub fn overwrite<P>(paths: P, overwrite: bool) -> Result<()>
105where
106 P: AsRef<[PathBuf]>,
107{
108 if paths.as_ref().iter().any(|p| p.exists()) {
109 println!("{:#?}, {}", paths.as_ref()[0], paths.as_ref()[0].exists());
110 if overwrite {
111 for path in paths.as_ref() {
112 delete_entry(&path).map_err(|e| {
113 ErrorBuilder::new()
114 .message("Failed to overwrite file/directory, please do it yourself")
115 .error(e)
116 .build()
117 })?
118 }
119 } else {
120 return Err(ErrorBuilder::new()
121 .message("The output file/directory already exists (use \"--overwrite\"/\"-O\" to force overwrite)")
122 .build());
123 }
124 };
125
126 Ok(())
127}
128
129pub fn delete_original<P>(path: P, delete_original: bool) -> Result<()>
130where
131 P: AsRef<Path>,
132{
133 if delete_original && path.as_ref().exists() {
134 delete_entry(path.as_ref()).map_err(|e| {
135 ErrorBuilder::new()
136 .message("Failed to delete original file/directory, please do it yourself")
137 .error(e)
138 .build()
139 })?;
140 }
141
142 Ok(())
143}
144
145pub fn delete_entry<P>(path: P) -> Result<()>
146where
147 P: AsRef<Path>,
148{
149 if path.as_ref().is_dir() {
150 fs::remove_dir_all(path.as_ref()).map_err(|e| {
151 ErrorBuilder::new()
152 .message("Failed to remove directory, please do it yourself")
153 .error(e)
154 .build()
155 })?;
156 } else if path.as_ref().is_file() {
157 fs::remove_file(path.as_ref()).map_err(|e| {
158 ErrorBuilder::new()
159 .message("Failed to remove file, please do it yourself")
160 .error(e)
161 .build()
162 })?;
163 }
164
165 Ok(())
166}
167
168pub fn encrypt_file<P1, P2>(
173 crypto: Crypto,
174 input_path: P1,
175 output_path: P2,
176 relative_path: Option<PathBuf>,
177) -> Result<()>
178where
179 P1: AsRef<Path>,
180 P2: AsRef<Path>,
181{
182 let mut source = OpenOptions::new()
183 .read(true)
184 .open(&input_path)
185 .map_err(|e| {
186 ErrorBuilder::new()
187 .message("Failed to read source file")
188 .error(e)
189 .build()
190 })?;
191
192 let mut dest = OpenOptions::new()
193 .write(true)
194 .create(true)
195 .open(&output_path)
196 .map_err(|e| {
197 ErrorBuilder::new()
198 .message("Failed to open/create destination file")
199 .error(e)
200 .build()
201 })?;
202
203 if let Some(p) = relative_path {
204 let metadata = metadata::encode(PathMetadata::new(p)).map_err(|e| {
205 ErrorBuilder::new()
206 .message("Failed to encode file metadata")
207 .error(e)
208 .build()
209 })?;
210
211 let encrypted_metadata = crypto.encrypt(metadata).map_err(|e| {
212 ErrorBuilder::new()
213 .message("Failed to encrypt metadata")
214 .error(e)
215 .build()
216 })?;
217
218 dest.write_all(
219 &[
220 (encrypted_metadata.len() as u16).to_be_bytes().as_ref(),
221 encrypted_metadata.as_ref(),
222 ]
223 .concat(),
224 )
225 .map_err(|e| {
226 ErrorBuilder::new()
227 .message("Failed to write metadata")
228 .error(e)
229 .build()
230 })?;
231 };
232
233 crypto.encrypt_io(&mut source, &mut dest).map_err(|e| {
234 ErrorBuilder::new()
235 .message(match e {
236 crypto::ErrorKind::AesError(_) => "Failed to encrypt file (key must be wrong)",
237 _ => "Failed to encrypt file",
238 })
239 .error(e)
240 .build()
241 })?;
242
243 Ok(())
244}
245
246pub fn decrypt_file<P>(crypto: Crypto, input_path: P, output_path: OutputDecPath) -> Result<()>
251where
252 P: AsRef<Path>,
253{
254 let mut source = OpenOptions::new()
255 .read(true)
256 .open(&input_path)
257 .map_err(|e| {
258 ErrorBuilder::new()
259 .message("Failed to read source file")
260 .error(e)
261 .build()
262 })?;
263
264 let output_path = match output_path {
265 OutputDecPath::Direct(p) => p,
266 OutputDecPath::Parent(p) => {
267 let mut len_bytes = [0u8; 2];
268 source.read_exact(&mut len_bytes).map_err(|e| {
269 ErrorBuilder::new()
270 .message("Failed to get encrypted metadata length")
271 .error(e)
272 .build()
273 })?;
274 let len = u16::from_be_bytes(len_bytes) as usize;
275 let mut metadata_bytes = vec![0u8; len];
276 source.read_exact(&mut metadata_bytes).map_err(|e| {
277 ErrorBuilder::new()
278 .message("Failed to get encrypted metadata")
279 .error(e)
280 .build()
281 })?;
282 let metadata = metadata::decode::<PathMetadata>(
283 &crypto.decrypt(&metadata_bytes).map_err(|e| {
284 ErrorBuilder::new()
285 .message("Failed to decrypt metadata")
286 .error(e)
287 .build()
288 })?,
289 )
290 .map_err(|e| {
291 ErrorBuilder::new()
292 .message("Failed to decode metadata")
293 .error(e)
294 .build()
295 })?;
296
297 let path = p.join(metadata.path());
298 if let Some(p) = path.parent() {
299 fs::create_dir_all(p).map_err(|e| {
300 ErrorBuilder::new()
301 .message("Failed to create sub-directory")
302 .error(e)
303 .build()
304 })?
305 };
306 path
307 }
308 };
309
310 let mut dest = OpenOptions::new()
311 .write(true)
312 .create(true)
313 .open(&output_path)
314 .map_err(|e| {
315 ErrorBuilder::new()
316 .message("Failed to open/create destination file")
317 .error(e)
318 .build()
319 })?;
320
321 crypto.decrypt_io(&mut source, &mut dest).map_err(|e| {
322 ErrorBuilder::new()
323 .message(match e {
324 crypto::ErrorKind::AesError(_) => "Failed to decrypt file (key must be wrong)",
325 _ => "Failed to decrypt file",
326 })
327 .error(e)
328 .build()
329 })?;
330
331 Ok(())
332}