use std::{
env, fs, io, path,
path::{Path, PathBuf},
};
#[cfg(unix)]
pub mod unix;
#[cfg(windows)]
pub mod windows;
pub mod sys {
use std::{
ffi::{OsStr, OsString},
path::{Path, PathBuf},
};
#[cfg(unix)]
use super::unix as sys;
#[cfg(windows)]
use super::windows as sys;
pub fn null_path() -> &'static Path {
Path::new(sys::NULL_PATH)
}
pub fn sanitize_str(str: impl AsRef<str>) -> String {
sanitize_os_str(str.as_ref())
.into_string()
.expect("Put UTF-8 in, got non-UTF-8 out")
}
pub fn sanitize_os_str(os_str: impl AsRef<OsStr>) -> OsString {
sys::sanitize_string(os_str)
}
pub fn sanitize_path(path: impl AsRef<Path>) -> PathBuf {
path.as_ref()
.components()
.map(|c| match c {
std::path::Component::Normal(os_str) => sanitize_os_str(os_str),
_ => c.as_os_str().to_owned(),
})
.collect()
}
pub fn is_protected_path(item: impl AsRef<Path>) -> Vec<PathBuf> {
let item = item.as_ref();
let mut protected_by = Vec::new();
if let Some(path) = sys::protected_paths().iter().find(|p| *p == item) {
protected_by.push(path.to_owned());
}
sys::protected_directories().iter().for_each(|path| {
if item.starts_with(path) {
protected_by.push(path.to_owned());
}
});
protected_by
}
}
pub fn absolutize_to_cwd(path: impl AsRef<Path>) -> io::Result<PathBuf> {
let abs = env::current_dir()?.join(path);
Ok(abs)
}
pub fn latest_existing_ancestor(path: impl AsRef<Path>) -> io::Result<PathBuf> {
let mut latest = Some(path.as_ref());
while let Some(path) = latest {
match path.symlink_metadata() {
Ok(meta) => {
if meta.is_dir() {
return Ok(path.to_path_buf());
}
latest = path.parent()
}
Err(err) if matches!(err.kind(), io::ErrorKind::NotFound) => latest = path.parent(),
Err(err) => return Err(err),
}
}
Err(io::Error::new(
io::ErrorKind::NotFound,
format!(
"No ancestor was found for {path}",
path = path.as_ref().display()
),
))
}
pub fn normalize_path(path: impl AsRef<Path>) -> PathBuf {
let mut normalized = PathBuf::new();
for component in path.as_ref().components() {
match component {
path::Component::CurDir => {}
path::Component::ParentDir => {
if !normalized.pop() {
normalized.push("..");
}
}
_ => normalized.push(component),
}
}
normalized.iter().collect()
}
pub fn follow_symlink(symlink: impl AsRef<Path>) -> io::Result<PathBuf> {
let target = fs::read_link(symlink.as_ref())?;
let real_target = if target.is_absolute() {
target
} else {
symlink
.as_ref()
.parent()
.unwrap_or(Path::new("/"))
.join(target)
};
if let Err(err) = real_target.symlink_metadata() {
if matches!(err.kind(), io::ErrorKind::NotFound) {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!(
"Target of symlink '{symlink}' does not exist ({target})",
symlink = symlink.as_ref().display(),
target = real_target.display()
),
));
} else {
return Err(err);
}
};
Ok(real_target)
}
pub fn follow_symlink_chain(symlink: impl AsRef<Path>) -> (Vec<PathBuf>, Option<io::Error>) {
let mut stack = vec![symlink.as_ref().to_path_buf()];
loop {
let target = match follow_symlink(stack.last().unwrap()) {
Ok(target) => normalize_path(target),
Err(err) => return (stack, Some(err)),
};
if stack.contains(&target) {
stack.push(target);
return (
stack,
Some(io::Error::new(
io::ErrorKind::TooManyLinks,
format!(
"{symlink} is a cyclical",
symlink = symlink.as_ref().display()
),
)),
);
}
if target.is_symlink() {
stack.push(target);
continue;
} else {
stack.push(target);
return (stack, None);
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn sanitize_is_lossless() {
use std::ffi::OsString;
use crate::paths::sys::sanitize_os_str;
let os_str = OsString::from("nothing_to_replace");
let sanitized = sanitize_os_str(os_str);
assert_eq!(sanitized, OsString::from("nothing_to_replace"))
}
#[test]
fn sanitize_strings() {
use crate::paths::sys::sanitize_str;
let sanitized = sanitize_str("nothing_to_replace");
assert_eq!(sanitized, String::from("nothing_to_replace"))
}
}