use crate::error::SoftPathError;
use std::fs;
use std::path::{Path, PathBuf};
pub(crate) trait PathOps {
fn as_path(&self) -> &Path;
fn create_file_impl(&self) -> Result<(), SoftPathError> {
let path = self.as_path().to_path_buf();
crate::utils::check_path_traversal(&path)?;
crate::utils::check_symlink_cycles(&path)?;
if let Some(parent) = path.parent() {
crate::utils::check_path_traversal(parent)?;
crate::utils::check_symlink_cycles(parent)?;
fs::create_dir_all(parent)?;
}
use std::fs::OpenOptions;
OpenOptions::new()
.write(true)
.create_new(true) .open(&path)
.map_err(|e| {
if e.kind() == std::io::ErrorKind::AlreadyExists {
std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("File already exists: {}", path.display())
)
} else {
e
}
})?;
Ok(())
}
fn write_string_impl(&self, contents: &str) -> Result<(), SoftPathError> {
let path = self.as_path().to_path_buf();
crate::utils::check_path_traversal(&path)?;
crate::utils::check_symlink_cycles(&path)?;
if let Some(parent) = path.parent() {
crate::utils::check_path_traversal(parent)?;
crate::utils::check_symlink_cycles(parent)?;
fs::create_dir_all(parent)?;
}
use std::io::Write;
let temp_path = path.with_extension("tmp");
{
let mut temp_file = fs::File::create(&temp_path)?;
temp_file.write_all(contents.as_bytes())?;
temp_file.sync_all()?; }
fs::rename(&temp_path, &path)?;
Ok(())
}
fn remove_impl(&self) -> Result<(), SoftPathError> {
let path = self.as_path().to_path_buf();
crate::utils::check_path_traversal(&path)?;
crate::utils::check_symlink_cycles(&path)?;
if path.is_dir() {
fs::remove_dir_all(path)?;
} else {
fs::remove_file(path)?;
}
Ok(())
}
fn is_empty_impl(&self) -> Result<bool, SoftPathError> {
let path = self.as_path().to_path_buf();
crate::utils::check_path_traversal(&path)?;
crate::utils::check_symlink_cycles(&path)?;
if path.is_file() {
Ok(fs::metadata(&path)?.len() == 0)
} else if path.is_dir() {
Ok(fs::read_dir(&path)?.next().is_none())
} else {
Ok(false)
}
}
fn is_hidden_impl(&self) -> Result<bool, SoftPathError> {
let path = self.as_path().to_path_buf();
crate::utils::check_path_traversal(&path)?;
crate::utils::check_symlink_cycles(&path)?;
#[cfg(windows)]
{
use std::os::windows::fs::MetadataExt;
let attr = fs::metadata(&path)?.file_attributes();
Ok((attr & 0x2) != 0) }
#[cfg(unix)]
{
Ok(path
.file_name()
.map(|s| s.to_string_lossy().to_string())
.is_some_and(|s| s.starts_with('.')))
}
}
}
impl PathOps for PathBuf {
fn as_path(&self) -> &Path {
self.as_path()
}
}
impl PathOps for &Path {
fn as_path(&self) -> &Path {
self
}
}