use std::error::Error;
use std::fmt;
use std::path::{Path, PathBuf};
const MAX_ERROR_PATH_LEN: usize = 256;
pub(crate) fn truncate_path_display(path: &Path, max_len: usize) -> String {
let path_str = path.to_string_lossy();
let char_count = path_str.chars().count();
if char_count <= max_len {
return path_str.into_owned();
}
let keep = max_len.saturating_sub(5) / 2;
let start: String = path_str.chars().take(keep).collect();
let mut tail_chars: Vec<char> = path_str.chars().rev().take(keep).collect();
tail_chars.reverse();
let end: String = tail_chars.into_iter().collect();
format!("{start}...{end}")
}
#[derive(Debug)]
pub enum StrictPathError {
InvalidRestriction {
restriction: PathBuf,
source: std::io::Error,
},
PathEscapesBoundary {
attempted_path: PathBuf,
restriction_boundary: PathBuf,
},
PathResolutionError {
path: PathBuf,
source: std::io::Error,
},
}
impl StrictPathError {
#[inline]
pub(crate) fn invalid_restriction(restriction: PathBuf, source: std::io::Error) -> Self {
Self::InvalidRestriction {
restriction,
source,
}
}
#[inline]
pub(crate) fn path_escapes_boundary(
attempted_path: PathBuf,
restriction_boundary: PathBuf,
) -> Self {
Self::PathEscapesBoundary {
attempted_path,
restriction_boundary,
}
}
#[inline]
pub(crate) fn path_resolution_error(path: PathBuf, source: std::io::Error) -> Self {
Self::PathResolutionError { path, source }
}
}
impl fmt::Display for StrictPathError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StrictPathError::InvalidRestriction { restriction, .. } => {
write!(
f,
"Invalid PathBoundary directory: {}",
restriction.display()
)
}
StrictPathError::PathEscapesBoundary {
attempted_path,
restriction_boundary,
} => {
let truncated_attempted = truncate_path_display(attempted_path, MAX_ERROR_PATH_LEN);
let truncated_boundary =
truncate_path_display(restriction_boundary, MAX_ERROR_PATH_LEN);
write!(
f,
"Path '{truncated_attempted}' escapes path restriction boundary '{truncated_boundary}'"
)
}
StrictPathError::PathResolutionError { path, .. } => {
write!(f, "Cannot resolve path: {}", path.display())
}
}
}
}
impl Error for StrictPathError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
StrictPathError::InvalidRestriction { source, .. }
| StrictPathError::PathResolutionError { source, .. } => Some(source),
StrictPathError::PathEscapesBoundary { .. } => None,
}
}
}
#[cfg(test)]
mod tests;