use super::{
files_map::{FilesMap, GetAttr},
metadata::FileMeta,
};
use crate::{Error, Result};
pub(crate) trait RealPath {
fn realpath(&self, fpath: &str) -> Result<String>;
}
impl RealPath for FilesMap {
fn realpath(&self, fpath: &str) -> Result<String> {
if fpath.is_empty() {
let msg = "Path cannot be empty".to_string();
return Err(Error::InvalidInput(msg));
}
if &fpath[0..1] != "/" {
let msg = format!("Relative path not supported. {fpath}");
return Err(Error::InvalidInput(msg));
}
let mut path: Vec<&str> = fpath.split('/').collect();
let mut nlinks = 0;
let mut newpath = Vec::<&str>::new();
let mut ended = false;
while !ended {
let mut iter = path.iter().peekable();
while let Some(cur) = iter.next() {
if *cur == "." {
continue;
} else if *cur == ".." {
if !newpath.is_empty() {
newpath.pop();
}
if newpath.is_empty() {
let msg = "Cannot ascend beyond root directory".to_string();
return Err(Error::ContentNotFound(msg));
}
} else {
newpath.push(cur);
let tmppath = newpath.join("/");
match &self.get(&tmppath) {
Some(fileitem) => {
let meta = FileMeta::from_file_item(fileitem);
if meta.is_symlink() {
nlinks += 1;
if nlinks > 16 {
let msg = "Too many levels of symbolic links".to_string();
return Err(Error::ContentNotFound(msg));
}
let target_str = &fileitem.getattr("symlink_target")?;
if target_str.is_empty() {
let msg = format!(
"Invalid/corrupted symlink '{tmppath}'. missing target.",
);
return Err(Error::ContentNotFound(msg));
}
let mut target_parts = target_str.split('/').collect();
let mut target: Vec<&str> = if &target_str[0..1] == "/" {
target_parts
} else {
newpath.pop();
newpath.append(&mut target_parts);
newpath
};
for n in &mut iter {
target.push(n)
}
path = target;
newpath = Vec::<&str>::new();
break;
} else if meta.is_dir() {
if iter.peek().is_none() {
ended = true;
}
} else {
ended = true;
}
}
None => {
if iter.peek().is_none() {
ended = true;
}
}
}
}
}
}
Ok(newpath.join("/"))
}
}