use std::io;
use std::path::{Path, PathBuf};
const WINDOWS_RESERVED_NAMES: &[&str] = &[
"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
"COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
];
pub fn canonicalize_safe(path: &Path) -> io::Result<PathBuf> {
let canonical = path.canonicalize()?;
debug_assert!(
canonical.is_absolute(),
"Canonicalized path must be absolute"
);
Ok(canonical)
}
#[must_use]
pub fn is_reserved_name(name: &str) -> bool {
if WINDOWS_RESERVED_NAMES
.iter()
.any(|r| r.eq_ignore_ascii_case(name))
{
return true;
}
if let Some(dot_pos) = name.find('.') {
let base = &name[..dot_pos];
return WINDOWS_RESERVED_NAMES
.iter()
.any(|r| r.eq_ignore_ascii_case(base));
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_canonicalize_safe() {
let temp = tempfile::TempDir::new().unwrap();
let file_path = temp.path().join("test.txt");
std::fs::write(&file_path, "test").unwrap();
let canonical = canonicalize_safe(&file_path).unwrap();
assert!(canonical.is_absolute());
assert!(canonical.exists());
}
#[test]
fn test_canonicalize_safe_nonexistent() {
let result = canonicalize_safe(Path::new("/nonexistent/file.txt"));
assert!(result.is_err());
}
#[test]
fn test_is_reserved_name_exact() {
assert!(is_reserved_name("CON"));
assert!(is_reserved_name("PRN"));
assert!(is_reserved_name("AUX"));
assert!(is_reserved_name("NUL"));
assert!(is_reserved_name("COM1"));
assert!(is_reserved_name("LPT1"));
}
#[test]
fn test_is_reserved_name_with_extension() {
assert!(is_reserved_name("CON.txt"));
assert!(is_reserved_name("COM1.log"));
}
#[test]
fn test_is_reserved_name_case_insensitive() {
assert!(is_reserved_name("con"));
assert!(is_reserved_name("CoM1"));
}
#[test]
fn test_is_reserved_name_not_reserved() {
assert!(is_reserved_name("con.txt"));
assert!(is_reserved_name("con"));
assert!(!is_reserved_name("LPT10")); assert!(!is_reserved_name("normal_file"));
}
#[test]
fn test_multiple_extensions() {
assert!(is_reserved_name("CON.txt.bak")); assert!(is_reserved_name("COM1.tar.gz"));
}
#[test]
fn test_no_extension() {
assert!(!is_reserved_name("CONFIG")); assert!(!is_reserved_name("README"));
}
}