1use crate::{
4 compression::{CompressionEngine, CompressionError},
5 crypto::{CryptoEngine, CryptoError, FileMetadata},
6 hybrid_crypto::{HybridCryptoEngine, HybridCryptoError},
7 models::{OperationParams, OperationResult, OperationType, TargetType},
8 progress::ProgressTracker,
9};
10use std::{
11 fs::{self, File},
12 io::{self, BufReader, BufWriter, Read, Write},
13 path::Path,
14};
15use thiserror::Error;
16
17#[derive(Error, Debug)]
19pub enum FileOperationError {
20 #[error("IO error: {0}")]
21 IoError(#[from] io::Error),
22 #[error("Crypto error: {0}")]
23 CryptoError(#[from] CryptoError),
24 #[error("Hybrid crypto error: {0}")]
25 HybridCryptoError(#[from] HybridCryptoError),
26 #[error("Compression error: {0}")]
27 CompressionError(#[from] CompressionError),
28 #[error("Invalid path: {0}")]
29 InvalidPath(String),
30 #[error("Path does not exist: {0}")]
31 PathNotFound(String),
32 #[error("Permission denied: {0}")]
33 PermissionDenied(String),
34}
35
36pub struct FileOperator {
38 crypto: CryptoEngine,
39 hybrid_crypto: HybridCryptoEngine,
40 compression: CompressionEngine,
41}
42
43impl Default for FileOperator {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl FileOperator {
50 pub fn new() -> Self {
52 Self {
53 crypto: CryptoEngine::new(),
54 hybrid_crypto: HybridCryptoEngine::new(),
55 compression: CompressionEngine::new(),
56 }
57 }
58
59 pub async fn process(&self, params: &OperationParams, password: &str) -> OperationResult {
61 let source = ¶ms.source;
62
63 if !source.exists() {
65 return OperationResult::failure(
66 source.clone(),
67 params.operation.clone(),
68 format!("Source path does not exist: {}", source.display()),
69 );
70 }
71
72 match params.target_type {
73 TargetType::File => self.process_file(params, password).await,
74 TargetType::Directory => self.process_directory(params, password).await,
75 }
76 }
77
78 async fn process_file(&self, params: &OperationParams, password: &str) -> OperationResult {
80 let source = ¶ms.source;
81 let destination = params.get_destination();
82
83 if let Some(parent) = destination.parent() {
85 if let Err(e) = fs::create_dir_all(parent) {
86 return OperationResult::failure(
87 source.clone(),
88 params.operation.clone(),
89 format!("Failed to create destination directory: {}", e),
90 );
91 }
92 }
93
94 let result = match params.operation {
95 OperationType::Encrypt => {
96 match self.encrypt_file(source, &destination, password, params).await {
97 Ok(bytes_processed) => OperationResult::success(
98 source.clone(),
99 destination,
100 bytes_processed,
101 params.operation.clone(),
102 params.compress,
103 ),
104 Err(e) => OperationResult::failure(
105 source.clone(),
106 params.operation.clone(),
107 e.to_string(),
108 ),
109 }
110 }
111 OperationType::Decrypt => {
112 match self.decrypt_file(source, &destination, password, params).await {
113 Ok((bytes_processed, metadata, checksum_verified)) => {
114 OperationResult::success_with_metadata(
115 source.clone(),
116 destination,
117 bytes_processed,
118 params.operation.clone(),
119 metadata.compressed,
120 Some(metadata.filename),
121 Some(checksum_verified),
122 )
123 }
124 Err(e) => OperationResult::failure(
125 source.clone(),
126 params.operation.clone(),
127 e.to_string(),
128 ),
129 }
130 }
131 OperationType::HybridEncrypt => {
132 match self.hybrid_encrypt_file(source, &destination, params).await {
133 Ok(bytes_processed) => OperationResult::success(
134 source.clone(),
135 destination,
136 bytes_processed,
137 params.operation.clone(),
138 params.compress,
139 ),
140 Err(e) => OperationResult::failure(
141 source.clone(),
142 params.operation.clone(),
143 e.to_string(),
144 ),
145 }
146 }
147 OperationType::HybridDecrypt => {
148 match self.hybrid_decrypt_file(source, &destination, params).await {
149 Ok((bytes_processed, metadata)) => OperationResult::success_with_metadata(
150 source.clone(),
151 destination,
152 bytes_processed,
153 params.operation.clone(),
154 metadata.compressed,
155 Some(metadata.filename),
156 Some(true), ),
158 Err(e) => OperationResult::failure(
159 source.clone(),
160 params.operation.clone(),
161 e.to_string(),
162 ),
163 }
164 }
165 };
166
167 result
168 }
169
170 async fn process_directory(&self, params: &OperationParams, password: &str) -> OperationResult {
172 let source = ¶ms.source;
173 let destination = params.get_destination();
174
175 match params.operation {
176 OperationType::Encrypt => {
177 match self.encrypt_directory(source, &destination, password, params).await {
179 Ok(bytes_processed) => OperationResult::success(
180 source.clone(),
181 destination,
182 bytes_processed,
183 params.operation.clone(),
184 true, ),
186 Err(e) => OperationResult::failure(
187 source.clone(),
188 params.operation.clone(),
189 e.to_string(),
190 ),
191 }
192 }
193 OperationType::Decrypt => {
194 match self.decrypt_directory(&source, &destination, password, params).await {
195 Ok(bytes_processed) => OperationResult::success(
196 source.clone(),
197 destination,
198 bytes_processed,
199 params.operation.clone(),
200 true, ),
202 Err(e) => OperationResult::failure(
203 source.clone(),
204 params.operation.clone(),
205 e.to_string(),
206 ),
207 }
208 }
209 OperationType::HybridEncrypt => {
210 println!("📁 Hybrid Directory Encryption");
212 println!("==============================");
213 println!("🔑 This will compress the directory and encrypt with hybrid encryption");
214
215 OperationResult::failure(
216 source.clone(),
217 params.operation.clone(),
218 "Hybrid directory encryption implementation pending".to_string(),
219 )
220 }
221 OperationType::HybridDecrypt => {
222 println!("📁 Hybrid Directory Decryption");
224 println!("==============================");
225
226 OperationResult::failure(
227 source.clone(),
228 params.operation.clone(),
229 "Hybrid directory decryption implementation pending".to_string(),
230 )
231 }
232 }
233 }
234
235 async fn encrypt_file(
237 &self,
238 source: &Path,
239 destination: &Path,
240 password: &str,
241 params: &OperationParams,
242 ) -> Result<u64, FileOperationError> {
243 let file_size = fs::metadata(source)?.len();
244 let progress = if params.show_progress {
245 Some(ProgressTracker::new(file_size, "Encrypting"))
246 } else {
247 None
248 };
249
250 let mut input = BufReader::new(File::open(source)?);
251 let mut file_data = Vec::new();
252
253 input.read_to_end(&mut file_data)?;
255
256 let data_to_encrypt = if params.compress {
258 progress.as_ref().map(|p| p.set_message("Compressing..."));
259 self.compression.compress(&file_data)?
260 } else {
261 file_data.clone()
262 };
263
264 let metadata = FileMetadata::from_file(source, &file_data, params.compress);
266
267 progress.as_ref().map(|p| p.set_message("Encrypting..."));
269 let encrypted_data = self.crypto.encrypt(&data_to_encrypt, password, metadata)?;
270
271 let mut output = BufWriter::new(File::create(destination)?);
273 output.write_all(&encrypted_data)?;
274 output.flush()?;
275
276 if let Some(progress) = &progress {
277 progress.inc(file_size);
278 progress.finish("Encryption complete");
279 }
280
281 if params.delete_source {
283 fs::remove_file(source)?;
284 }
285
286 Ok(encrypted_data.len() as u64)
287 }
288
289 async fn hybrid_encrypt_file(
291 &self,
292 source: &Path,
293 destination: &Path,
294 params: &OperationParams,
295 ) -> Result<u64, FileOperationError> {
296 println!("🔑 Hybrid Encryption Mode");
298 println!("=========================");
299
300 let file_size = fs::metadata(source)?.len();
301 let progress = if params.show_progress {
302 Some(ProgressTracker::new(file_size, "Hybrid Encrypting"))
303 } else {
304 None
305 };
306
307 let mut input = BufReader::new(File::open(source)?);
308 let mut file_data = Vec::new();
309
310 input.read_to_end(&mut file_data)?;
312
313 let data_to_encrypt = if params.compress {
315 progress.as_ref().map(|p| p.set_message("Compressing..."));
316 self.compression.compress(&file_data)?
317 } else {
318 file_data.clone()
319 };
320
321 let metadata = FileMetadata::from_file(source, &file_data, params.compress);
323
324 progress.as_ref().map(|p| p.set_message("Hybrid encrypting..."));
326 let encrypted_data = self.hybrid_crypto.encrypt(&data_to_encrypt, params.public_key_path.as_deref(), metadata)?;
327
328 let final_destination = if destination.extension().is_none() {
330 destination.with_extension("hsf")
331 } else {
332 destination.to_path_buf()
333 };
334
335 let mut output = BufWriter::new(File::create(&final_destination)?);
337 output.write_all(&encrypted_data)?;
338 output.flush()?;
339
340 if let Some(progress) = &progress {
341 progress.inc(file_size);
342 progress.finish("Hybrid encryption complete");
343 }
344
345 println!("✅ Successfully encrypted file using hybrid encryption");
347 println!("📁 Output: {}", final_destination.display());
348
349 if params.delete_source {
351 fs::remove_file(source)?;
352 }
353
354 Ok(encrypted_data.len() as u64)
355 }
356
357 async fn hybrid_decrypt_file(
359 &self,
360 source: &Path,
361 destination: &Path,
362 params: &OperationParams,
363 ) -> Result<(u64, FileMetadata), FileOperationError> {
364 println!("🔓 Hybrid Decryption Mode");
366 println!("=========================");
367
368 let file_size = fs::metadata(source)?.len();
369 let progress = if params.show_progress {
370 Some(ProgressTracker::new(file_size, "Hybrid Decrypting"))
371 } else {
372 None
373 };
374
375 let mut input = BufReader::new(File::open(source)?);
377 let mut encrypted_data = Vec::new();
378 input.read_to_end(&mut encrypted_data)?;
379
380 progress.as_ref().map(|p| p.set_message("Hybrid decrypting..."));
382 let (decrypted_data, metadata) = self.hybrid_crypto.decrypt(&encrypted_data, params.private_key_path.as_deref())?;
383
384 let final_data = if metadata.compressed {
386 progress.as_ref().map(|p| p.set_message("Decompressing..."));
387 self.compression.decompress(&decrypted_data)?
388 } else {
389 decrypted_data
390 };
391
392 let final_destination = if source.extension() == Some(std::ffi::OsStr::new("hsf")) {
394 destination.with_file_name(source.file_stem().unwrap_or(source.as_os_str()))
395 } else {
396 destination.to_path_buf()
397 };
398
399 let mut output = BufWriter::new(File::create(&final_destination)?);
401 output.write_all(&final_data)?;
402 output.flush()?;
403
404 if let Some(progress) = &progress {
405 progress.inc(file_size);
406 progress.finish("Hybrid decryption complete");
407 }
408
409 println!("✅ Successfully decrypted file using hybrid decryption");
411 println!("📁 Output: {}", final_destination.display());
412
413 if params.delete_source {
415 fs::remove_file(source)?;
416 }
417
418 Ok((final_data.len() as u64, metadata))
419 }
420
421 async fn decrypt_file(
423 &self,
424 source: &Path,
425 destination: &Path,
426 password: &str,
427 params: &OperationParams,
428 ) -> Result<(u64, FileMetadata, bool), FileOperationError> {
429 let file_size = fs::metadata(source)?.len();
430 let progress = if params.show_progress {
431 Some(ProgressTracker::new(file_size, "Decrypting"))
432 } else {
433 None
434 };
435
436 let mut input = BufReader::new(File::open(source)?);
437 let mut encrypted_data = Vec::new();
438
439 input.read_to_end(&mut encrypted_data)?;
441
442 if let Some(progress) = &progress {
443 progress.inc(file_size);
444 progress.set_message("Decrypting...");
445 }
446
447 let (decrypted_data, metadata) = match self.crypto.decrypt(&encrypted_data, password) {
449 Ok((data, meta)) => (data, Some(meta)),
450 Err(_) => {
451 let data = self.crypto.decrypt_legacy(&encrypted_data, password)?;
453 (data, None)
454 }
455 };
456
457 let final_data = if let Some(ref meta) = metadata {
459 if meta.compressed {
460 if let Some(progress) = &progress {
461 progress.set_message("Decompressing...");
462 }
463 self.compression.decompress(&decrypted_data)?
464 } else {
465 decrypted_data
466 }
467 } else if params.compress {
468 if let Some(progress) = &progress {
470 progress.set_message("Decompressing...");
471 }
472 match self.compression.decompress(&decrypted_data) {
473 Ok(decompressed) => decompressed,
474 Err(_) => decrypted_data, }
476 } else {
477 decrypted_data
478 };
479
480 let mut output = BufWriter::new(File::create(destination)?);
482 output.write_all(&final_data)?;
483 output.flush()?;
484
485 if let Some(progress) = &progress {
486 progress.finish("Decryption complete");
487 }
488
489 if params.delete_source {
491 fs::remove_file(source)?;
492 }
493
494 let checksum_verified = if let Some(ref meta) = metadata {
496 if params.verify_checksum {
497 meta.verify_checksum(&final_data)
498 } else {
499 true }
501 } else {
502 true };
504
505 let result_metadata = metadata.unwrap_or_else(|| {
506 FileMetadata::new("unknown".to_string(), [0u8; 32], params.compress)
508 });
509
510 Ok((final_data.len() as u64, result_metadata, checksum_verified))
511 }
512
513 async fn encrypt_directory(
515 &self,
516 source: &Path,
517 destination: &Path,
518 password: &str,
519 params: &OperationParams,
520 ) -> Result<u64, FileOperationError> {
521 let progress = if params.show_progress {
522 Some(ProgressTracker::new_spinner("Encrypting directory"))
523 } else {
524 None
525 };
526
527 progress.as_ref().map(|p| p.set_message("Creating archive..."));
529 let archive_data = self.create_directory_archive(source)?;
530
531 let metadata = FileMetadata::from_file(source, &archive_data, true); progress.as_ref().map(|p| p.set_message("Encrypting archive..."));
536 let encrypted_data = self.crypto.encrypt(&archive_data, password, metadata)?;
537
538 let mut output = BufWriter::new(File::create(destination)?);
540 output.write_all(&encrypted_data)?;
541 output.flush()?;
542
543 if let Some(progress) = &progress {
544 progress.finish("Directory encryption complete");
545 }
546
547 Ok(encrypted_data.len() as u64)
548 }
549
550 async fn decrypt_directory(
552 &self,
553 source: &Path,
554 destination: &Path,
555 password: &str,
556 params: &OperationParams,
557 ) -> Result<u64, FileOperationError> {
558 let progress = if params.show_progress {
559 Some(ProgressTracker::new_spinner("Decrypting directory"))
560 } else {
561 None
562 };
563
564 progress.as_ref().map(|p| p.set_message("Reading encrypted file..."));
566 let mut encrypted_data = Vec::new();
567 let mut input = BufReader::new(File::open(source)?);
568 input.read_to_end(&mut encrypted_data)?;
569
570 progress.as_ref().map(|p| p.set_message("Decrypting..."));
571 let (archive_data, _metadata) = match self.crypto.decrypt(&encrypted_data, password) {
572 Ok((data, meta)) => (data, Some(meta)),
573 Err(_) => {
574 let data = self.crypto.decrypt_legacy(&encrypted_data, password)?;
576 (data, None)
577 }
578 };
579
580 progress.as_ref().map(|p| p.set_message("Extracting archive..."));
582 self.extract_directory_archive(&archive_data, destination)?;
583
584 if let Some(progress) = &progress {
585 progress.finish("Directory decryption complete");
586 }
587
588 Ok(archive_data.len() as u64)
589 }
590
591 fn create_directory_archive(&self, source: &Path) -> Result<Vec<u8>, FileOperationError> {
593 use flate2::write::GzEncoder;
594
595 let archive_buffer = Vec::new();
596 let encoder = GzEncoder::new(archive_buffer, flate2::Compression::default());
597 let mut tar = tar::Builder::new(encoder);
598
599 tar.append_dir_all(".", source)
601 .map_err(|e| FileOperationError::IoError(e))?;
602
603 let encoder = tar.into_inner()
604 .map_err(|e| FileOperationError::IoError(e))?;
605
606 let archive_data = encoder.finish()
607 .map_err(|e| FileOperationError::IoError(e))?;
608
609 Ok(archive_data)
610 }
611
612 fn extract_directory_archive(&self, archive_data: &[u8], destination: &Path) -> Result<(), FileOperationError> {
614 use flate2::read::GzDecoder;
615
616 fs::create_dir_all(destination)?;
618
619 let decoder = GzDecoder::new(archive_data);
620 let mut archive = tar::Archive::new(decoder);
621
622 archive.unpack(destination)
623 .map_err(|e| FileOperationError::IoError(e))?;
624
625 Ok(())
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632 use tempfile::TempDir;
633 use std::fs;
634
635 #[tokio::test]
636 async fn test_file_encryption_decryption() {
637 let temp_dir = TempDir::new().unwrap();
638 let source_file = temp_dir.path().join("test.txt");
639 let encrypted_file = temp_dir.path().join("test.sf");
640 let decrypted_file = temp_dir.path().join("test_decrypted.txt");
641
642 fs::write(&source_file, b"Hello, World! This is a test file.").unwrap();
644
645 let operator = FileOperator::new();
646 let password = "test_password_123";
647
648 let encrypt_params = OperationParams::new(
650 OperationType::Encrypt,
651 TargetType::File,
652 source_file.clone(),
653 ).with_destination(encrypted_file.clone())
654 .with_progress(false);
655
656 let result = operator.process(&encrypt_params, password).await;
657 assert!(result.success, "Encryption failed: {:?}", result.error);
658 assert!(encrypted_file.exists());
659
660 let decrypt_params = OperationParams::new(
662 OperationType::Decrypt,
663 TargetType::File,
664 encrypted_file,
665 ).with_destination(decrypted_file.clone())
666 .with_progress(false);
667
668 let result = operator.process(&decrypt_params, password).await;
669 assert!(result.success, "Decryption failed: {:?}", result.error);
670 assert!(decrypted_file.exists());
671
672 let original_content = fs::read(&source_file).unwrap();
674 let decrypted_content = fs::read(&decrypted_file).unwrap();
675 assert_eq!(original_content, decrypted_content);
676 }
677
678 #[tokio::test]
679 async fn test_file_encryption_with_compression() {
680 let temp_dir = TempDir::new().unwrap();
681 let source_file = temp_dir.path().join("test.txt");
682 let encrypted_file = temp_dir.path().join("test.sf.gz");
683 let decrypted_file = temp_dir.path().join("test_decrypted.txt");
684
685 let content = "Hello, World! ".repeat(1000);
687 fs::write(&source_file, content.as_bytes()).unwrap();
688
689 let operator = FileOperator::new();
690 let password = "test_password_123";
691
692 let encrypt_params = OperationParams::new(
694 OperationType::Encrypt,
695 TargetType::File,
696 source_file.clone(),
697 ).with_destination(encrypted_file.clone())
698 .with_compression(true)
699 .with_progress(false);
700
701 let result = operator.process(&encrypt_params, password).await;
702 assert!(result.success);
703 assert!(encrypted_file.exists());
704
705 let decrypt_params = OperationParams::new(
707 OperationType::Decrypt,
708 TargetType::File,
709 encrypted_file,
710 ).with_destination(decrypted_file.clone())
711 .with_compression(true)
712 .with_progress(false);
713
714 let result = operator.process(&decrypt_params, password).await;
715 assert!(result.success);
716 assert!(decrypted_file.exists());
717
718 let original_content = fs::read(&source_file).unwrap();
720 let decrypted_content = fs::read(&decrypted_file).unwrap();
721 assert_eq!(original_content, decrypted_content);
722 }
723
724 #[tokio::test]
725 async fn test_wrong_password() {
726 let temp_dir = TempDir::new().unwrap();
727 let source_file = temp_dir.path().join("test.txt");
728 let encrypted_file = temp_dir.path().join("test.sf");
729
730 fs::write(&source_file, b"Secret content").unwrap();
731
732 let operator = FileOperator::new();
733
734 let encrypt_params = OperationParams::new(
736 OperationType::Encrypt,
737 TargetType::File,
738 source_file,
739 ).with_destination(encrypted_file.clone())
740 .with_progress(false);
741
742 let result = operator.process(&encrypt_params, "correct_password").await;
743 assert!(result.success);
744
745 let decrypt_params = OperationParams::new(
747 OperationType::Decrypt,
748 TargetType::File,
749 encrypted_file,
750 ).with_progress(false);
751
752 let result = operator.process(&decrypt_params, "wrong_password").await;
753 assert!(!result.success);
754 assert!(result.error.is_some());
755 }
756}