use std::net::IpAddr;
use std::path::{Path, PathBuf};
use crate::error::FsvError;
use crate::types::FileInfo;
pub fn get_local_ips() -> Vec<IpAddr> {
let is_valid = |ip: &IpAddr| matches!(ip, IpAddr::V4(v4) if !v4.is_link_local());
let mut ips: Vec<IpAddr> = local_ip_address::local_ip()
.ok()
.filter(|ip| is_valid(ip))
.into_iter()
.collect();
if let Ok(interfaces) = local_ip_address::list_afinet_netifas() {
for (_name, ip) in interfaces {
if is_valid(&ip) && !ips.contains(&ip) {
ips.push(ip);
}
}
}
if ips.is_empty() {
ips.push(IpAddr::V4(std::net::Ipv4Addr::LOCALHOST));
}
ips
}
pub fn resolve_safe_path(root_path: &Path, relative: Option<&str>) -> Result<PathBuf, FsvError> {
let canonical_root = root_path.canonicalize().map_err(|e| {
FsvError::PathError(format!("Failed to canonicalize root path: {}", e))
})?;
let Some(rel) = relative else {
return Ok(canonical_root);
};
let rel_cleaned = rel.trim_start_matches(['/', '\\']);
let joined = canonical_root.join(rel_cleaned);
let canonical_target = joined.canonicalize().map_err(|_| FsvError::NotFound)?;
if canonical_target.starts_with(&canonical_root) {
Ok(canonical_target)
} else {
Err(FsvError::AccessDenied)
}
}
pub fn get_file_info(canonical_root: &Path, target_path: &Path) -> Result<FileInfo, FsvError> {
let metadata = target_path.metadata()?;
let name = target_path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default();
let path = target_path
.strip_prefix(canonical_root)
.unwrap_or(Path::new(""))
.to_string_lossy()
.replace('\\', "/");
let modified = metadata
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::SystemTime::UNIX_EPOCH).ok())
.map(|d| d.as_secs());
Ok(FileInfo {
name,
path,
is_dir: metadata.is_dir(),
size: metadata.len(),
modified,
})
}