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