typed_path/unix/
non_utf8.rs1mod components;
2
3use core::fmt;
4use core::hash::Hasher;
5
6pub use components::*;
7
8use super::constants::*;
9use crate::common::CheckedPathError;
10use crate::no_std_compat::*;
11use crate::typed::{TypedPath, TypedPathBuf};
12use crate::{private, Components, Encoding, Path, PathBuf};
13
14pub type UnixPath = Path<UnixEncoding>;
16
17pub type UnixPathBuf = PathBuf<UnixEncoding>;
19
20#[derive(Copy, Clone)]
22pub struct UnixEncoding;
23
24impl private::Sealed for UnixEncoding {}
25
26impl Encoding for UnixEncoding {
27 type Components<'a> = UnixComponents<'a>;
28
29 fn label() -> &'static str {
30 "unix"
31 }
32
33 fn components(path: &[u8]) -> Self::Components<'_> {
34 UnixComponents::new(path)
35 }
36
37 fn hash<H: Hasher>(path: &[u8], h: &mut H) {
38 let mut component_start = 0;
39 let mut bytes_hashed = 0;
40
41 for i in 0..path.len() {
42 let is_sep = path[i] == SEPARATOR as u8;
43 if is_sep {
44 if i > component_start {
45 let to_hash = &path[component_start..i];
46 h.write(to_hash);
47 bytes_hashed += to_hash.len();
48 }
49
50 component_start = i + 1;
53
54 let tail = &path[component_start..];
55
56 component_start += match tail {
57 [b'.'] => 1,
58 [b'.', sep, ..] if *sep == SEPARATOR as u8 => 1,
59 _ => 0,
60 };
61 }
62 }
63
64 if component_start < path.len() {
65 let to_hash = &path[component_start..];
66 h.write(to_hash);
67 bytes_hashed += to_hash.len();
68 }
69
70 h.write_usize(bytes_hashed);
71 }
72
73 fn push(current_path: &mut Vec<u8>, path: &[u8]) {
74 if path.is_empty() {
75 return;
76 }
77
78 if Self::components(path).is_absolute() {
84 current_path.clear();
85 } else if !current_path.is_empty() && !current_path.ends_with(&[SEPARATOR as u8]) {
86 current_path.push(SEPARATOR as u8);
87 }
88
89 current_path.extend_from_slice(path);
90 }
91
92 fn push_checked(current_path: &mut Vec<u8>, path: &[u8]) -> Result<(), CheckedPathError> {
93 let mut normal_cnt = 0;
98 for component in UnixPath::new(path).components() {
99 match component {
100 UnixComponent::RootDir => return Err(CheckedPathError::UnexpectedRoot),
101 UnixComponent::ParentDir if normal_cnt == 0 => {
102 return Err(CheckedPathError::PathTraversalAttack)
103 }
104 UnixComponent::ParentDir => normal_cnt -= 1,
105 UnixComponent::Normal(bytes) => {
106 for b in bytes {
107 if DISALLOWED_FILENAME_BYTES.contains(b) {
108 return Err(CheckedPathError::InvalidFilename);
109 }
110 }
111 normal_cnt += 1;
112 }
113 _ => continue,
114 }
115 }
116
117 Self::push(current_path, path);
118 Ok(())
119 }
120}
121
122impl fmt::Debug for UnixEncoding {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.debug_struct("UnixEncoding").finish()
125 }
126}
127
128impl fmt::Display for UnixEncoding {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 write!(f, "UnixEncoding")
131 }
132}
133
134impl<T> Path<T>
135where
136 T: Encoding,
137{
138 pub fn has_unix_encoding(&self) -> bool {
149 T::label() == UnixEncoding::label()
150 }
151
152 pub fn with_unix_encoding(&self) -> PathBuf<UnixEncoding> {
156 self.with_encoding()
157 }
158
159 pub fn with_unix_encoding_checked(&self) -> Result<PathBuf<UnixEncoding>, CheckedPathError> {
164 self.with_encoding_checked()
165 }
166}
167
168impl UnixPath {
169 pub fn to_typed_path(&self) -> TypedPath<'_> {
170 TypedPath::unix(self)
171 }
172
173 pub fn to_typed_path_buf(&self) -> TypedPathBuf {
174 TypedPathBuf::from_unix(self)
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn push_should_replace_current_path_with_provided_path_if_provided_path_is_absolute() {
184 let mut current_path = vec![];
186 UnixEncoding::push(&mut current_path, b"/abc");
187 assert_eq!(current_path, b"/abc");
188
189 let mut current_path = b"some/path".to_vec();
191 UnixEncoding::push(&mut current_path, b"/abc");
192 assert_eq!(current_path, b"/abc");
193
194 let mut current_path = b"/some/path/".to_vec();
196 UnixEncoding::push(&mut current_path, b"/abc");
197 assert_eq!(current_path, b"/abc");
198 }
199
200 #[test]
201 fn push_should_append_path_to_current_path_with_a_separator_if_provided_path_is_relative() {
202 let mut current_path = vec![];
204 UnixEncoding::push(&mut current_path, b"abc");
205 assert_eq!(current_path, b"abc");
206
207 let mut current_path = b"some/path".to_vec();
209 UnixEncoding::push(&mut current_path, b"abc");
210 assert_eq!(current_path, b"some/path/abc");
211
212 let mut current_path = b"some/path/".to_vec();
214 UnixEncoding::push(&mut current_path, b"abc");
215 assert_eq!(current_path, b"some/path/abc");
216 }
217
218 #[test]
219 fn push_checked_should_fail_if_providing_an_absolute_path() {
220 let mut current_path = vec![];
222 assert_eq!(
223 UnixEncoding::push_checked(&mut current_path, b"/abc"),
224 Err(CheckedPathError::UnexpectedRoot)
225 );
226 assert_eq!(current_path, b"");
227
228 let mut current_path = b"some/path".to_vec();
230 assert_eq!(
231 UnixEncoding::push_checked(&mut current_path, b"/abc"),
232 Err(CheckedPathError::UnexpectedRoot)
233 );
234 assert_eq!(current_path, b"some/path");
235
236 let mut current_path = b"/some/path/".to_vec();
238 assert_eq!(
239 UnixEncoding::push_checked(&mut current_path, b"/abc"),
240 Err(CheckedPathError::UnexpectedRoot)
241 );
242 assert_eq!(current_path, b"/some/path/");
243 }
244
245 #[test]
246 fn push_checked_should_fail_if_providing_a_path_with_disallowed_filename_bytes() {
247 let mut current_path = vec![];
249 assert_eq!(
250 UnixEncoding::push_checked(&mut current_path, b"some/inva\0lid/path"),
251 Err(CheckedPathError::InvalidFilename)
252 );
253 assert_eq!(current_path, b"");
254
255 let mut current_path = b"some/path".to_vec();
258 assert_eq!(
259 UnixEncoding::push_checked(&mut current_path, b"some/inva\0lid/path"),
260 Err(CheckedPathError::InvalidFilename)
261 );
262 assert_eq!(current_path, b"some/path");
263
264 let mut current_path = b"/some/path/".to_vec();
267 assert_eq!(
268 UnixEncoding::push_checked(&mut current_path, b"some/inva\0lid/path"),
269 Err(CheckedPathError::InvalidFilename)
270 );
271 assert_eq!(current_path, b"/some/path/");
272 }
273
274 #[test]
275 fn push_checked_should_fail_if_providing_a_path_that_would_escape_the_current_path() {
276 let mut current_path = vec![];
278 assert_eq!(
279 UnixEncoding::push_checked(&mut current_path, b".."),
280 Err(CheckedPathError::PathTraversalAttack)
281 );
282 assert_eq!(current_path, b"");
283
284 let mut current_path = b"some/path".to_vec();
286 assert_eq!(
287 UnixEncoding::push_checked(&mut current_path, b".."),
288 Err(CheckedPathError::PathTraversalAttack)
289 );
290 assert_eq!(current_path, b"some/path");
291
292 let mut current_path = b"/some/path/".to_vec();
294 assert_eq!(
295 UnixEncoding::push_checked(&mut current_path, b".."),
296 Err(CheckedPathError::PathTraversalAttack)
297 );
298 assert_eq!(current_path, b"/some/path/");
299 }
300
301 #[test]
302 fn push_checked_should_append_path_to_current_path_with_a_separator_if_does_not_violate_rules()
303 {
304 let mut current_path = vec![];
307 assert_eq!(
308 UnixEncoding::push_checked(&mut current_path, b"abc/../def/."),
309 Ok(()),
310 );
311 assert_eq!(current_path, b"abc/../def/.");
312
313 let mut current_path = b"some/path".to_vec();
314 assert_eq!(
315 UnixEncoding::push_checked(&mut current_path, b"abc/../def/."),
316 Ok(()),
317 );
318 assert_eq!(current_path, b"some/path/abc/../def/.");
319
320 let mut current_path = b"/some/path/".to_vec();
321 assert_eq!(
322 UnixEncoding::push_checked(&mut current_path, b"abc/../def/."),
323 Ok(()),
324 );
325 assert_eq!(current_path, b"/some/path/abc/../def/.");
326 }
327}