Merge pull request 'fix(#1): sanitize paths - block traversal, resolve to absolute' (#17) from fix/issue-1 into master

This commit is contained in:
jarianc 2026-07-05 09:45:38 -05:00
commit 2481ed644b

View File

@ -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<PathBuf, Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
}
// 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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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(())