typed_path/windows/
utf8.rs

1mod components;
2
3use core::fmt;
4use core::hash::Hasher;
5
6pub use components::*;
7
8use crate::common::CheckedPathError;
9use crate::no_std_compat::*;
10use crate::typed::{Utf8TypedPath, Utf8TypedPathBuf};
11use crate::{private, Encoding, Utf8Encoding, Utf8Path, Utf8PathBuf, WindowsEncoding};
12
13/// Represents a Windows-specific [`Utf8Path`]
14pub type Utf8WindowsPath = Utf8Path<Utf8WindowsEncoding>;
15
16/// Represents a Windows-specific [`Utf8PathBuf`]
17pub type Utf8WindowsPathBuf = Utf8PathBuf<Utf8WindowsEncoding>;
18
19/// Represents a Windows-specific [`Utf8Encoding`]
20#[derive(Copy, Clone)]
21pub struct Utf8WindowsEncoding;
22
23impl private::Sealed for Utf8WindowsEncoding {}
24
25impl Utf8Encoding for Utf8WindowsEncoding {
26    type Components<'a> = Utf8WindowsComponents<'a>;
27
28    fn label() -> &'static str {
29        "windows"
30    }
31
32    fn components(path: &str) -> Self::Components<'_> {
33        Utf8WindowsComponents::new(path)
34    }
35
36    fn hash<H: Hasher>(path: &str, h: &mut H) {
37        WindowsEncoding::hash(path.as_bytes(), h);
38    }
39
40    fn push(current_path: &mut String, path: &str) {
41        unsafe {
42            WindowsEncoding::push(current_path.as_mut_vec(), path.as_bytes());
43        }
44    }
45
46    fn push_checked(current_path: &mut String, path: &str) -> Result<(), CheckedPathError> {
47        unsafe { WindowsEncoding::push_checked(current_path.as_mut_vec(), path.as_bytes()) }
48    }
49}
50
51impl fmt::Debug for Utf8WindowsEncoding {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        f.debug_struct("Utf8WindowsEncoding").finish()
54    }
55}
56
57impl fmt::Display for Utf8WindowsEncoding {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "Utf8WindowsEncoding")
60    }
61}
62
63impl<T> Utf8Path<T>
64where
65    T: Utf8Encoding,
66{
67    /// Returns true if the encoding for the path is for Windows.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use typed_path::{Utf8UnixPath, Utf8WindowsPath};
73    ///
74    /// assert!(!Utf8UnixPath::new("/some/path").has_windows_encoding());
75    /// assert!(Utf8WindowsPath::new(r"\some\path").has_windows_encoding());
76    /// ```
77    pub fn has_windows_encoding(&self) -> bool {
78        T::label() == Utf8WindowsEncoding::label()
79    }
80
81    /// Creates an owned [`Utf8PathBuf`] like `self` but using [`Utf8WindowsEncoding`].
82    ///
83    /// See [`Utf8Path::with_encoding`] for more information.
84    pub fn with_windows_encoding(&self) -> Utf8PathBuf<Utf8WindowsEncoding> {
85        self.with_encoding()
86    }
87
88    /// Creates an owned [`Utf8PathBuf`] like `self` but using [`Utf8WindowsEncoding`], ensuring it
89    /// is a valid Windows path.
90    ///
91    /// See [`Utf8Path::with_encoding_checked`] for more information.
92    pub fn with_windows_encoding_checked(
93        &self,
94    ) -> Result<Utf8PathBuf<Utf8WindowsEncoding>, CheckedPathError> {
95        self.with_encoding_checked()
96    }
97}
98
99impl Utf8WindowsPath {
100    pub fn to_typed_path(&self) -> Utf8TypedPath<'_> {
101        Utf8TypedPath::windows(self)
102    }
103
104    pub fn to_typed_path_buf(&self) -> Utf8TypedPathBuf {
105        Utf8TypedPathBuf::from_windows(self)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn push_checked_should_fail_if_providing_an_absolute_path() {
115        // Empty current path will fail when pushing an absolute path
116        let mut current_path = String::new();
117        assert_eq!(
118            Utf8WindowsEncoding::push_checked(&mut current_path, r"\abc"),
119            Err(CheckedPathError::UnexpectedRoot)
120        );
121        assert_eq!(current_path, "");
122
123        // Non-empty relative current path will fail when pushing an absolute path
124        let mut current_path = String::from(r"some\path");
125        assert_eq!(
126            Utf8WindowsEncoding::push_checked(&mut current_path, r"\abc"),
127            Err(CheckedPathError::UnexpectedRoot)
128        );
129        assert_eq!(current_path, r"some\path");
130
131        // Non-empty absolute current path will fail when pushing an absolute path
132        let mut current_path = String::from(r"\some\path\");
133        assert_eq!(
134            Utf8WindowsEncoding::push_checked(&mut current_path, r"\abc"),
135            Err(CheckedPathError::UnexpectedRoot)
136        );
137        assert_eq!(current_path, r"\some\path\");
138    }
139
140    #[test]
141    fn push_checked_should_fail_if_providing_a_path_with_an_embedded_prefix() {
142        // Empty current path will fail when pushing a path with a prefix
143        let mut current_path = String::new();
144        assert_eq!(
145            Utf8WindowsEncoding::push_checked(&mut current_path, r"C:abc"),
146            Err(CheckedPathError::UnexpectedPrefix)
147        );
148        assert_eq!(current_path, "");
149
150        // Non-empty relative current path will fail when pushing a path with a prefix
151        let mut current_path = String::from(r"some\path");
152        assert_eq!(
153            Utf8WindowsEncoding::push_checked(&mut current_path, r"C:abc"),
154            Err(CheckedPathError::UnexpectedPrefix)
155        );
156        assert_eq!(current_path, r"some\path");
157
158        // Non-empty absolute current path will fail when pushing a path with a prefix
159        let mut current_path = String::from(r"\some\path\");
160        assert_eq!(
161            Utf8WindowsEncoding::push_checked(&mut current_path, r"C:abc"),
162            Err(CheckedPathError::UnexpectedPrefix)
163        );
164        assert_eq!(current_path, r"\some\path\");
165    }
166
167    #[test]
168    fn push_checked_should_fail_if_providing_a_path_with_disallowed_filename_bytes() {
169        // Empty current path will fail when pushing a path containing disallowed filename bytes
170        let mut current_path = String::new();
171        assert_eq!(
172            Utf8WindowsEncoding::push_checked(&mut current_path, r"some\inva|lid\path"),
173            Err(CheckedPathError::InvalidFilename)
174        );
175        assert_eq!(current_path, "");
176
177        // Non-empty relative current path will fail when pushing a path containing disallowed
178        // filename bytes
179        let mut current_path = String::from(r"some\path");
180        assert_eq!(
181            Utf8WindowsEncoding::push_checked(&mut current_path, r"some\inva|lid\path"),
182            Err(CheckedPathError::InvalidFilename)
183        );
184        assert_eq!(current_path, r"some\path");
185
186        // Non-empty absolute current path will fail when pushing a path containing disallowed
187        // filename bytes
188        let mut current_path = String::from(r"\some\path\");
189        assert_eq!(
190            Utf8WindowsEncoding::push_checked(&mut current_path, r"some\inva|lid\path"),
191            Err(CheckedPathError::InvalidFilename)
192        );
193        assert_eq!(current_path, r"\some\path\");
194    }
195
196    #[test]
197    fn push_checked_should_fail_if_providing_a_path_that_would_escape_the_current_path() {
198        // Empty current path will fail when pushing a path that would escape
199        let mut current_path = String::new();
200        assert_eq!(
201            Utf8WindowsEncoding::push_checked(&mut current_path, ".."),
202            Err(CheckedPathError::PathTraversalAttack)
203        );
204        assert_eq!(current_path, "");
205
206        // Non-empty relative current path will fail when pushing a path that would escape
207        let mut current_path = String::from(r"some\path");
208        assert_eq!(
209            Utf8WindowsEncoding::push_checked(&mut current_path, ".."),
210            Err(CheckedPathError::PathTraversalAttack)
211        );
212        assert_eq!(current_path, r"some\path");
213
214        // Non-empty absolute current path will fail when pushing a path that would escape
215        let mut current_path = String::from(r"\some\path\");
216        assert_eq!(
217            Utf8WindowsEncoding::push_checked(&mut current_path, ".."),
218            Err(CheckedPathError::PathTraversalAttack)
219        );
220        assert_eq!(current_path, r"\some\path\");
221    }
222
223    #[test]
224    fn push_checked_should_append_path_to_current_path_with_a_separator_if_does_not_violate_rules()
225    {
226        // Pushing a path that contains parent dirs, but does not escape the current path,
227        // should succeed
228        let mut current_path = String::new();
229        assert_eq!(
230            Utf8WindowsEncoding::push_checked(&mut current_path, r"abc\..\def\."),
231            Ok(()),
232        );
233        assert_eq!(current_path, r"abc\..\def\.");
234
235        let mut current_path = String::from(r"some\path");
236        assert_eq!(
237            Utf8WindowsEncoding::push_checked(&mut current_path, r"abc\..\def\."),
238            Ok(()),
239        );
240        assert_eq!(current_path, r"some\path\abc\..\def\.");
241
242        let mut current_path = String::from(r"\some\path\");
243        assert_eq!(
244            Utf8WindowsEncoding::push_checked(&mut current_path, r"abc\..\def\."),
245            Ok(()),
246        );
247        assert_eq!(current_path, r"\some\path\abc\..\def\.");
248    }
249}