1 //! Path validation for the purpose of the [`fs`] module. This is decoupled from
2 //! [`Path`] and [`PathBuf`], as the Rust standard library also does it this
3 //! way. Instead, the FS implementation is responsible for that.
4 //!
5 //! [`PathBuf`]: super::PathBuf
6 //! [`fs`]: crate::fs
7 
8 use super::Path;
9 use crate::fs::CHARACTER_DENY_LIST;
10 use crate::Char16;
11 use core::fmt::{self, Display, Formatter};
12 
13 /// Errors related to file paths.
14 #[derive(Debug, Clone, Eq, PartialEq)]
15 pub enum PathError {
16     /// The path is empty / points to nothing.
17     Empty,
18     /// A component of the path is empty, i.e., two separators without content
19     /// in between were found.
20     EmptyComponent,
21     /// There are illegal characters in the path.
22     IllegalChar(Char16),
23 }
24 
25 impl Display for PathError {
fmt(&self, f: &mut Formatter<'_>) -> fmt::Result26     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
27         match self {
28             Self::Empty => write!(f, "path is empty"),
29             Self::EmptyComponent => write!(f, "path contains an empty component"),
30             Self::IllegalChar(c) => {
31                 write!(
32                     f,
33                     "path contains an illegal character (value {})",
34                     u16::from(*c)
35                 )
36             }
37         }
38     }
39 }
40 
41 #[cfg(feature = "unstable")]
42 impl core::error::Error for PathError {}
43 
44 /// Validates a path for the needs of the [`fs`] module.
45 ///
46 /// [`fs`]: crate::fs
validate_path<P: AsRef<Path>>(path: P) -> Result<(), PathError>47 pub fn validate_path<P: AsRef<Path>>(path: P) -> Result<(), PathError> {
48     let path = path.as_ref();
49     if path.is_empty() {
50         return Err(PathError::Empty);
51     }
52     for component in path.components() {
53         if component.is_empty() {
54             return Err(PathError::EmptyComponent);
55         } else if let Some(char) = component
56             .as_slice()
57             .iter()
58             .find(|c| CHARACTER_DENY_LIST.contains(c))
59         {
60             return Err(PathError::IllegalChar(*char));
61         }
62     }
63     Ok(())
64 }
65 
66 #[cfg(test)]
67 mod tests {
68     use super::*;
69     use crate::fs::PathBuf;
70     use crate::{cstr16, CString16};
71 
72     #[test]
test_validate_path()73     fn test_validate_path() {
74         validate_path(cstr16!("hello\\foo\\bar")).unwrap();
75 
76         let err = validate_path(cstr16!("hello\\f>oo\\bar")).unwrap_err();
77         assert_eq!(err, PathError::IllegalChar(CHARACTER_DENY_LIST[6]));
78 
79         let err = validate_path(cstr16!("hello\\\\bar")).unwrap_err();
80         assert_eq!(err, PathError::EmptyComponent);
81 
82         let empty_cstring16 = CString16::try_from("").unwrap();
83         let path = PathBuf::from(empty_cstring16);
84         let err = validate_path(path).unwrap_err();
85         assert_eq!(err, PathError::Empty)
86     }
87 }
88