strict_path/error/
mod.rs

1// Content copied from original src/error/mod.rs
2use std::error::Error;
3use std::fmt;
4use std::path::{Path, PathBuf};
5
6const MAX_ERROR_PATH_LEN: usize = 256;
7
8pub(crate) fn truncate_path_display(path: &Path, max_len: usize) -> String {
9    let path_str = path.to_string_lossy();
10    let char_count = path_str.chars().count();
11    if char_count <= max_len {
12        return path_str.into_owned();
13    }
14    let keep = max_len.saturating_sub(5) / 2;
15    let start: String = path_str.chars().take(keep).collect();
16    let mut tail_chars: Vec<char> = path_str.chars().rev().take(keep).collect();
17    tail_chars.reverse();
18    let end: String = tail_chars.into_iter().collect();
19    format!("{start}...{end}")
20}
21
22/// Errors produced by PathBoundary creation and path validation.
23#[derive(Debug)]
24pub enum StrictPathError {
25    /// The PathBoundary root is invalid (missing, not a directory, or IO error).
26    InvalidRestriction {
27        restriction: PathBuf,
28        source: std::io::Error,
29    },
30    /// The attempted path would resolve outside the PathBoundary boundary.
31    PathEscapesBoundary {
32        attempted_path: PathBuf,
33        restriction_boundary: PathBuf,
34    },
35    /// Canonicalization/resolution failed for the given path.
36    PathResolutionError {
37        path: PathBuf,
38        source: std::io::Error,
39    },
40    #[cfg(windows)]
41    /// A component resembles a Windows 8.3 short name (potential ambiguity).
42    WindowsShortName {
43        component: std::ffi::OsString,
44        original: PathBuf,
45        checked_at: PathBuf,
46    },
47}
48
49impl StrictPathError {
50    #[inline]
51    pub(crate) fn invalid_restriction(restriction: PathBuf, source: std::io::Error) -> Self {
52        Self::InvalidRestriction {
53            restriction,
54            source,
55        }
56    }
57    #[inline]
58    pub(crate) fn path_escapes_boundary(
59        attempted_path: PathBuf,
60        restriction_boundary: PathBuf,
61    ) -> Self {
62        Self::PathEscapesBoundary {
63            attempted_path,
64            restriction_boundary,
65        }
66    }
67    #[inline]
68    pub(crate) fn path_resolution_error(path: PathBuf, source: std::io::Error) -> Self {
69        Self::PathResolutionError { path, source }
70    }
71    #[cfg(windows)]
72    #[inline]
73    pub(crate) fn windows_short_name(
74        component: std::ffi::OsString,
75        original: PathBuf,
76        checked_at: PathBuf,
77    ) -> Self {
78        Self::WindowsShortName {
79            component,
80            original,
81            checked_at,
82        }
83    }
84}
85
86impl fmt::Display for StrictPathError {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        match self {
89            StrictPathError::InvalidRestriction { restriction, .. } => {
90                write!(
91                    f,
92                    "Invalid PathBoundary directory: {}",
93                    restriction.display()
94                )
95            }
96            StrictPathError::PathEscapesBoundary {
97                attempted_path,
98                restriction_boundary,
99            } => {
100                let truncated_attempted = truncate_path_display(attempted_path, MAX_ERROR_PATH_LEN);
101                let truncated_boundary =
102                    truncate_path_display(restriction_boundary, MAX_ERROR_PATH_LEN);
103                write!(
104                    f,
105                    "Path '{truncated_attempted}' escapes path restriction boundary '{truncated_boundary}'"
106                )
107            }
108            StrictPathError::PathResolutionError { path, .. } => {
109                write!(f, "Cannot resolve path: {}", path.display())
110            }
111            #[cfg(windows)]
112            StrictPathError::WindowsShortName {
113                component,
114                original,
115                checked_at,
116            } => {
117                let original_trunc = truncate_path_display(original, MAX_ERROR_PATH_LEN);
118                let checked_trunc = truncate_path_display(checked_at, MAX_ERROR_PATH_LEN);
119                write!(
120                    f,
121                    "Windows 8.3 short filename component '{}' rejected at '{}' for original '{}'",
122                    component.to_string_lossy(),
123                    checked_trunc,
124                    original_trunc
125                )
126            }
127        }
128    }
129}
130
131impl Error for StrictPathError {
132    fn source(&self) -> Option<&(dyn Error + 'static)> {
133        match self {
134            StrictPathError::InvalidRestriction { source, .. }
135            | StrictPathError::PathResolutionError { source, .. } => Some(source),
136            StrictPathError::PathEscapesBoundary { .. } => None,
137            #[cfg(windows)]
138            StrictPathError::WindowsShortName { .. } => None,
139        }
140    }
141}
142
143#[cfg(test)]
144mod tests;