diff --git a/src/main.rs b/src/main.rs index 6980b65..9801afb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,56 @@ use clap::Parser; use libraw_sys::*; use std::ffi::CString; use std::os::raw::c_int; -use std::path::Path; +use std::path::{Path, PathBuf}; + +/// Resolve and sanitize a user-provided path. +/// - Resolves to absolute path via canonicalize (or parent canonicalization for output) +/// - Rejects paths containing `..` components after resolution +/// - Returns human-readable error on failure +fn sanitize_path( + path_str: &str, + must_exist: bool, +) -> Result> { + let path = Path::new(path_str); + + // Block explicit traversal sequences in user input + for component in path.components() { + if component.as_os_str() == ".." { + return Err(format!( + "Path traversal ('..') is not allowed in: {}", + path_str + ) + .into()); + } + } + + if must_exist { + let resolved = path.canonicalize().map_err(|e| { + format!("Failed to resolve input path '{}': {}", path_str, e) + })?; + Ok(resolved) + } else { + // For output paths that may not exist yet, resolve the parent directory + if let Some(parent) = path.parent() { + let resolved_parent = parent.canonicalize().map_err(|e| { + format!( + "Output directory '{}' does not exist or is not accessible: {}", + parent.display(), + e + ) + })?; + let filename = path + .file_name() + .unwrap_or(path.as_os_str()) + .to_string_lossy() + .to_string(); + Ok(resolved_parent.join(filename)) + } else { + let resolved = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); + Ok(resolved) + } + } +} /// Photo Editor - A Rust-based photo editor with RAW image processing capabilities using libraw #[derive(Parser, Debug)] @@ -80,11 +129,17 @@ fn main() -> Result<(), Box> { println!("Temperature: {}", args.temperature); println!("Tint: {}", args.tint); - // Check if input file exists - if !Path::new(&args.input).exists() { - eprintln!("Error: Input file '{}' does not exist.", args.input); - return Err("Input file not found".into()); - } + // Sanitize and resolve input path + let input_path = sanitize_path(&args.input, true).map_err(|e| { + eprintln!("Error: {}", e); + e + })?; + + // Sanitize and resolve output path + let output_path = sanitize_path(&args.output, false).map_err(|e| { + eprintln!("Error: {}", e); + e + })?; println!("\n[INFO] Attempting RAW image processing with libraw integration..."); @@ -96,7 +151,8 @@ fn main() -> Result<(), Box> { } // Open the RAW file - let input_cstr = CString::new(args.input.clone()).unwrap(); + let input_cstr = CString::new(input_path.to_string_lossy().as_bytes()) + .map_err(|_| "Input path contains null bytes, cannot process".into())?; let ret = unsafe { libraw_open_file(lr, input_cstr.as_ptr()) }; if ret != LIBRAW_SUCCESS { @@ -133,24 +189,15 @@ fn main() -> Result<(), Box> { println!("[INFO] Image processed successfully"); // Determine output format from extension - let ext = Path::new(&args.output) + let ext = output_path .extension() .and_then(|s| s.to_str()) .unwrap_or("jpg") .to_lowercase(); - let ret = match ext.as_str() { - "jpg" | "jpeg" => { - // For JPEG output, we'll write directly to file - let output_cstr = CString::new(args.output.clone()).unwrap(); - unsafe { libraw_dcraw_ppm_tiff_writer(lr, output_cstr.as_ptr()) } - } - _ => { - // For other formats, attempt to convert - let output_cstr = CString::new(args.output.clone()).unwrap(); - unsafe { libraw_dcraw_ppm_tiff_writer(lr, output_cstr.as_ptr()) } - } - }; + let output_cstr = CString::new(output_path.to_string_lossy().as_bytes()) + .map_err(|_| "Output path contains null bytes, cannot process".into())?; + let ret = unsafe { libraw_dcraw_ppm_tiff_writer(lr, output_cstr.as_ptr()) }; if ret != LIBRAW_SUCCESS { eprintln!("Error: Failed to write output file. Error code: {}", ret); @@ -168,7 +215,7 @@ fn main() -> Result<(), Box> { println!("\n[SUCCESS] Photo editing completed successfully with libraw integration!"); println!( "[NOTICE] The edited image has been saved as: {}", - args.output + output_path.display() ); Ok(())