1use crate::config::ImageConfig;
4use crate::error::{ImageError, ImageResult};
5use crate::formats::ImageFormat;
6use crate::models::ImageMetadata;
7use sha2::{Digest, Sha256};
8use std::path::{Path, PathBuf};
9
10pub struct ImageHandler {
12 config: ImageConfig,
13}
14
15impl ImageHandler {
16 pub fn new(config: ImageConfig) -> Self {
18 Self { config }
19 }
20
21 pub fn with_default_config() -> ImageResult<Self> {
23 let config = ImageConfig::load_with_hierarchy()?;
24 Ok(Self::new(config))
25 }
26
27 fn sanitize_path(path: &Path) -> ImageResult<PathBuf> {
37 let normalized = path.canonicalize().map_err(|_| {
39 ImageError::PathTraversalError
40 })?;
41
42 if !normalized.is_absolute() {
44 return Err(ImageError::PathTraversalError);
45 }
46
47 Ok(normalized)
48 }
49
50 pub fn read_image(&self, path: &Path) -> ImageResult<ImageMetadata> {
67 let sanitized_path = Self::sanitize_path(path)?;
69
70 if !sanitized_path.exists() {
72 return Err(ImageError::InvalidFile(
73 format!("File does not exist: {}", sanitized_path.display()),
74 ));
75 }
76
77 if !sanitized_path.is_file() {
79 return Err(ImageError::InvalidFile(
80 "Path is not a file".to_string(),
81 ));
82 }
83
84 let format = ImageFormat::validate_file(
86 &sanitized_path,
87 self.config.analysis.max_image_size_mb,
88 )?;
89
90 let (width, height) = ImageFormat::extract_metadata(&sanitized_path)?;
92
93 let hash = self.calculate_file_hash(&sanitized_path)?;
95
96 let metadata = std::fs::metadata(&sanitized_path)?;
98 let size_bytes = metadata.len();
99
100 Ok(ImageMetadata::new(
101 sanitized_path,
102 format,
103 size_bytes,
104 width,
105 height,
106 hash,
107 ))
108 }
109
110 fn calculate_file_hash(&self, path: &Path) -> ImageResult<String> {
120 let mut file = std::fs::File::open(path)?;
121 let mut hasher = Sha256::new();
122 std::io::copy(&mut file, &mut hasher)?;
123 let hash = hasher.finalize();
124 Ok(format!("{:x}", hash))
125 }
126
127 pub fn validate_format(&self, format: ImageFormat) -> ImageResult<()> {
142 let format_str = format.as_str();
143 if self.config.is_format_supported(format_str) {
144 Ok(())
145 } else {
146 Err(ImageError::FormatNotSupported(
147 format!(
148 "Format '{}' is not supported. Supported formats: {}",
149 format_str,
150 self.config.supported_formats_string()
151 ),
152 ))
153 }
154 }
155
156 pub fn handle_dropped_files(
170 &self,
171 paths: &[PathBuf],
172 ) -> (Vec<ImageMetadata>, Vec<ImageError>) {
173 let mut images = Vec::new();
174 let mut errors = Vec::new();
175
176 for path in paths {
177 match self.read_image(path) {
178 Ok(metadata) => images.push(metadata),
179 Err(e) => errors.push(e),
180 }
181 }
182
183 (images, errors)
184 }
185
186 pub fn config(&self) -> &ImageConfig {
188 &self.config
189 }
190
191 pub fn extract_paths_from_event(event_data: &str) -> Vec<PathBuf> {
206 let lines: Vec<&str> = event_data.lines().collect();
208
209 if lines.len() > 1 || (lines.len() == 1 && !lines[0].contains(' ')) {
210 return lines
212 .into_iter()
213 .filter(|line| !line.is_empty())
214 .map(PathBuf::from)
215 .collect();
216 }
217
218 event_data
220 .split_whitespace()
221 .filter(|s| !s.is_empty())
222 .map(PathBuf::from)
223 .collect()
224 }
225
226 pub fn check_file_accessible(path: &Path) -> ImageResult<()> {
240 if !path.exists() {
242 return Err(ImageError::InvalidFile(
243 format!("File does not exist: {}", path.display()),
244 ));
245 }
246
247 if !path.is_file() {
249 return Err(ImageError::InvalidFile(
250 "Path is not a file".to_string(),
251 ));
252 }
253
254 std::fs::File::open(path).map_err(|e| {
256 ImageError::InvalidFile(
257 format!("Cannot read file: {}", e),
258 )
259 })?;
260
261 Ok(())
262 }
263
264 pub fn process_drag_drop_event(
279 &self,
280 event_data: &str,
281 ) -> (Vec<ImageMetadata>, Vec<ImageError>) {
282 let paths = Self::extract_paths_from_event(event_data);
283
284 let mut images = Vec::new();
285 let mut errors = Vec::new();
286
287 for path in paths {
288 if let Err(e) = Self::check_file_accessible(&path) {
290 errors.push(e);
291 continue;
292 }
293
294 match self.read_image(&path) {
296 Ok(metadata) => images.push(metadata),
297 Err(e) => errors.push(e),
298 }
299 }
300
301 (images, errors)
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use std::io::Write;
309 use tempfile::NamedTempFile;
310
311 #[test]
312 fn test_handler_creation() {
313 let config = ImageConfig::default();
314 let handler = ImageHandler::new(config);
315 assert!(handler.config.cache.enabled);
316 }
317
318 #[test]
319 fn test_handler_default_creation() {
320 let handler = ImageHandler::with_default_config();
321 assert!(handler.is_ok());
322 }
323
324 #[test]
325 fn test_sanitize_path_valid() {
326 let temp_dir = tempfile::tempdir().unwrap();
327 let temp_path = temp_dir.path().to_path_buf();
328
329 let result = ImageHandler::sanitize_path(&temp_path);
330 assert!(result.is_ok());
331 }
332
333 #[test]
334 fn test_sanitize_path_nonexistent() {
335 let path = PathBuf::from("/nonexistent/path/that/does/not/exist");
336 let result = ImageHandler::sanitize_path(&path);
337 assert!(result.is_err());
338 }
339
340 #[test]
341 fn test_read_image_nonexistent_file() {
342 let config = ImageConfig::default();
343 let handler = ImageHandler::new(config);
344
345 let result = handler.read_image(Path::new("/nonexistent/image.png"));
346 assert!(result.is_err());
347 }
348
349 #[test]
350 fn test_read_image_valid_png() {
351 let config = ImageConfig::default();
352 let handler = ImageHandler::new(config);
353
354 let mut temp_file = NamedTempFile::new().unwrap();
356 let png_header = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
357 temp_file.write_all(&png_header).unwrap();
358 temp_file.flush().unwrap();
359
360 let result = handler.read_image(temp_file.path());
362 assert!(result.is_err());
364 }
365
366 #[test]
367 fn test_validate_format_supported() {
368 let config = ImageConfig::default();
369 let handler = ImageHandler::new(config);
370
371 let result = handler.validate_format(ImageFormat::Png);
372 assert!(result.is_ok());
373 }
374
375 #[test]
376 fn test_validate_format_all_supported() {
377 let config = ImageConfig::default();
378 let handler = ImageHandler::new(config);
379
380 assert!(handler.validate_format(ImageFormat::Png).is_ok());
381 assert!(handler.validate_format(ImageFormat::Jpeg).is_ok());
382 assert!(handler.validate_format(ImageFormat::Gif).is_ok());
383 assert!(handler.validate_format(ImageFormat::WebP).is_ok());
384 }
385
386 #[test]
387 fn test_handle_dropped_files_empty() {
388 let config = ImageConfig::default();
389 let handler = ImageHandler::new(config);
390
391 let (images, errors) = handler.handle_dropped_files(&[]);
392 assert_eq!(images.len(), 0);
393 assert_eq!(errors.len(), 0);
394 }
395
396 #[test]
397 fn test_handle_dropped_files_nonexistent() {
398 let config = ImageConfig::default();
399 let handler = ImageHandler::new(config);
400
401 let paths = vec![
402 PathBuf::from("/nonexistent/image1.png"),
403 PathBuf::from("/nonexistent/image2.jpg"),
404 ];
405
406 let (images, errors) = handler.handle_dropped_files(&paths);
407 assert_eq!(images.len(), 0);
408 assert_eq!(errors.len(), 2);
409 }
410
411 #[test]
412 fn test_calculate_file_hash() {
413 let config = ImageConfig::default();
414 let handler = ImageHandler::new(config);
415
416 let mut temp_file = NamedTempFile::new().unwrap();
418 temp_file.write_all(b"test content").unwrap();
419 temp_file.flush().unwrap();
420
421 let hash = handler.calculate_file_hash(temp_file.path());
422 assert!(hash.is_ok());
423
424 let hash_str = hash.unwrap();
425 assert_eq!(hash_str.len(), 64);
427 }
428
429 #[test]
430 fn test_calculate_file_hash_consistency() {
431 let config = ImageConfig::default();
432 let handler = ImageHandler::new(config);
433
434 let mut temp_file = NamedTempFile::new().unwrap();
436 temp_file.write_all(b"test content").unwrap();
437 temp_file.flush().unwrap();
438
439 let hash1 = handler.calculate_file_hash(temp_file.path()).unwrap();
440 let hash2 = handler.calculate_file_hash(temp_file.path()).unwrap();
441
442 assert_eq!(hash1, hash2);
444 }
445
446 #[test]
447 fn test_extract_paths_from_event_single_line() {
448 let event_data = "/path/to/image.png";
449 let paths = ImageHandler::extract_paths_from_event(event_data);
450
451 assert_eq!(paths.len(), 1);
452 assert_eq!(paths[0], PathBuf::from("/path/to/image.png"));
453 }
454
455 #[test]
456 fn test_extract_paths_from_event_multiple_lines() {
457 let event_data = "/path/to/image1.png\n/path/to/image2.jpg\n/path/to/image3.gif";
458 let paths = ImageHandler::extract_paths_from_event(event_data);
459
460 assert_eq!(paths.len(), 3);
461 assert_eq!(paths[0], PathBuf::from("/path/to/image1.png"));
462 assert_eq!(paths[1], PathBuf::from("/path/to/image2.jpg"));
463 assert_eq!(paths[2], PathBuf::from("/path/to/image3.gif"));
464 }
465
466 #[test]
467 fn test_extract_paths_from_event_space_separated() {
468 let event_data = "/path/to/image1.png /path/to/image2.jpg";
469 let paths = ImageHandler::extract_paths_from_event(event_data);
470
471 assert_eq!(paths.len(), 2);
472 assert_eq!(paths[0], PathBuf::from("/path/to/image1.png"));
473 assert_eq!(paths[1], PathBuf::from("/path/to/image2.jpg"));
474 }
475
476 #[test]
477 fn test_extract_paths_from_event_empty() {
478 let event_data = "";
479 let paths = ImageHandler::extract_paths_from_event(event_data);
480
481 assert_eq!(paths.len(), 0);
482 }
483
484 #[test]
485 fn test_check_file_accessible_nonexistent() {
486 let result = ImageHandler::check_file_accessible(Path::new("/nonexistent/file.png"));
487 assert!(result.is_err());
488 }
489
490 #[test]
491 fn test_check_file_accessible_valid() {
492 let temp_file = NamedTempFile::new().unwrap();
493 let result = ImageHandler::check_file_accessible(temp_file.path());
494 assert!(result.is_ok());
495 }
496
497 #[test]
498 fn test_check_file_accessible_directory() {
499 let temp_dir = tempfile::tempdir().unwrap();
500 let result = ImageHandler::check_file_accessible(temp_dir.path());
501 assert!(result.is_err());
502 }
503
504 #[test]
505 fn test_process_drag_drop_event_empty() {
506 let config = ImageConfig::default();
507 let handler = ImageHandler::new(config);
508
509 let (images, errors) = handler.process_drag_drop_event("");
510 assert_eq!(images.len(), 0);
511 assert_eq!(errors.len(), 0);
512 }
513
514 #[test]
515 fn test_process_drag_drop_event_nonexistent_files() {
516 let config = ImageConfig::default();
517 let handler = ImageHandler::new(config);
518
519 let event_data = "/nonexistent/image1.png\n/nonexistent/image2.jpg";
520 let (images, errors) = handler.process_drag_drop_event(event_data);
521
522 assert_eq!(images.len(), 0);
523 assert_eq!(errors.len(), 2);
524 }
525
526 #[test]
527 fn test_process_drag_drop_event_mixed_valid_invalid() {
528 let config = ImageConfig::default();
529 let handler = ImageHandler::new(config);
530
531 let temp_file = NamedTempFile::new().unwrap();
532 let temp_path = temp_file.path().to_string_lossy().to_string();
533
534 let event_data = format!("{}\n/nonexistent/image.png", temp_path);
535 let (_images, errors) = handler.process_drag_drop_event(&event_data);
536
537 assert_eq!(errors.len(), 2);
539 }
540
541 #[test]
542 fn test_error_message_format_not_supported() {
543 let config = ImageConfig::default();
544 let _handler = ImageHandler::new(config);
545
546 let mut limited_config = ImageConfig::default();
548 limited_config.formats.supported = vec!["png".to_string()];
549 let limited_handler = ImageHandler::new(limited_config);
550
551 let result = limited_handler.validate_format(ImageFormat::Jpeg);
552 assert!(result.is_err());
553
554 let error_msg = result.unwrap_err().to_string();
555 assert!(error_msg.contains("not supported"));
556 assert!(error_msg.contains("png"));
557 }
558
559 #[test]
560 fn test_error_message_file_too_large() {
561 let config = ImageConfig::default();
562 let handler = ImageHandler::new(config);
563
564 let mut temp_file = NamedTempFile::new().unwrap();
566 let large_data = vec![0u8; 11 * 1024 * 1024]; temp_file.write_all(&large_data).unwrap();
568 temp_file.flush().unwrap();
569
570 let result = handler.read_image(temp_file.path());
571 assert!(result.is_err());
572
573 let error_msg = result.unwrap_err().to_string();
574 assert!(error_msg.contains("too large") || error_msg.contains("exceeds"));
575 }
576
577 #[test]
578 fn test_error_message_invalid_file() {
579 let config = ImageConfig::default();
580 let handler = ImageHandler::new(config);
581
582 let mut temp_file = NamedTempFile::new().unwrap();
584 temp_file.write_all(b"not an image").unwrap();
585 temp_file.flush().unwrap();
586
587 let result = handler.read_image(temp_file.path());
588 assert!(result.is_err());
589
590 let error_msg = result.unwrap_err().to_string();
591 assert!(error_msg.contains("Invalid") || error_msg.contains("invalid"));
592 }
593
594 #[test]
595 fn test_path_sanitization_prevents_traversal() {
596 let suspicious_path = PathBuf::from("../../../etc/passwd");
598 let result = ImageHandler::sanitize_path(&suspicious_path);
599
600 assert!(result.is_err());
602 }
603
604 #[test]
605 fn test_error_message_path_traversal() {
606 let config = ImageConfig::default();
607 let handler = ImageHandler::new(config);
608
609 let suspicious_path = Path::new("../../../etc/passwd");
611 let result = handler.read_image(suspicious_path);
612
613 assert!(result.is_err());
614 let error_msg = result.unwrap_err().to_string();
615 assert!(error_msg.contains("traversal") || error_msg.contains("does not exist"));
617 }
618
619 #[test]
620 fn test_format_validation_error_includes_supported_formats() {
621 let config = ImageConfig::default();
622 let handler = ImageHandler::new(config);
623
624 let result = handler.validate_format(ImageFormat::Png);
626 assert!(result.is_ok());
627
628 let formats_str = handler.config().supported_formats_string();
630 assert!(formats_str.contains("png"));
631 assert!(formats_str.contains("jpg"));
632 assert!(formats_str.contains("gif"));
633 assert!(formats_str.contains("webp"));
634 }
635
636 #[test]
637 fn test_read_image_with_valid_metadata() {
638 let config = ImageConfig::default();
639 let handler = ImageHandler::new(config);
640
641 let mut temp_file = NamedTempFile::new().unwrap();
643 let png_header = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
644 temp_file.write_all(&png_header).unwrap();
645 temp_file.flush().unwrap();
646
647 let result = handler.read_image(temp_file.path());
650
651 if let Err(e) = result {
653 let error_msg = e.to_string();
654 assert!(error_msg.contains("Invalid") || error_msg.contains("invalid"));
655 }
656 }
657
658 #[test]
659 fn test_multiple_files_error_collection() {
660 let config = ImageConfig::default();
661 let handler = ImageHandler::new(config);
662
663 let paths = vec![
664 PathBuf::from("/nonexistent/image1.png"),
665 PathBuf::from("/nonexistent/image2.jpg"),
666 PathBuf::from("/nonexistent/image3.gif"),
667 ];
668
669 let (_images, errors) = handler.handle_dropped_files(&paths);
670
671 assert_eq!(errors.len(), 3);
673
674 for error in errors {
676 let error_msg = error.to_string();
677 assert!(error_msg.contains("does not exist") || error_msg.contains("Invalid"));
678 }
679 }
680}