use std::path::{Path, PathBuf};
use crate::utils::path::error::PathError;
use crate::utils::path::validate::validate_no_traversal;
use crate::utils::path::validate::validate_relative_only;
pub fn home_dir() -> Option<PathBuf> {
let path = std::env::var("HOME")
.ok()
.map(PathBuf::from)
.or_else(|| std::env::var("USERPROFILE").ok().map(PathBuf::from))?;
if path.exists() && path.is_dir() {
Some(path)
} else {
None
}
}
pub fn home_relative(path: impl AsRef<Path>) -> String {
let path = path.as_ref();
if let Some(home) = home_dir() {
if let Ok(stripped) = path.strip_prefix(&home) {
return format!("~/{}", stripped.display());
}
}
path.display().to_string()
}
pub fn expand_home(path: impl AsRef<Path>) -> Result<PathBuf, PathError> {
let path = path.as_ref();
let path_str = path.to_string_lossy();
if let Some(rest) = path_str.strip_prefix("~/") {
if let Some(home) = home_dir() {
let rest_path = Path::new(rest);
validate_no_traversal(rest_path)?;
validate_relative_only(rest_path)?;
let expanded = home.join(rest_path);
if home.exists() {
if let Ok(home_canonical) = home.canonicalize() {
if expanded.exists() {
if let Ok(expanded_canonical) = expanded.canonicalize() {
if !expanded_canonical.starts_with(&home_canonical) {
return Err(PathError::OutsideBounds);
}
}
}
}
}
return Ok(expanded);
}
} else if path_str == "~" {
if let Some(home) = home_dir() {
return Ok(home);
}
}
Ok(path.to_path_buf())
}