typed_path/windows/
non_utf8.rs1mod components;
2
3use core::fmt;
4use core::hash::{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, Component, Components, Encoding, Path, PathBuf};
13
14pub type WindowsPath = Path<WindowsEncoding>;
16
17pub type WindowsPathBuf = PathBuf<WindowsEncoding>;
19
20#[derive(Copy, Clone)]
22pub struct WindowsEncoding;
23
24impl private::Sealed for WindowsEncoding {}
25
26impl Encoding for WindowsEncoding {
27 type Components<'a> = WindowsComponents<'a>;
28
29 fn label() -> &'static str {
30 "windows"
31 }
32
33 fn components(path: &[u8]) -> Self::Components<'_> {
34 WindowsComponents::new(path)
35 }
36
37 fn hash<H: Hasher>(path: &[u8], h: &mut H) {
38 let (prefix_len, verbatim) = match Self::components(path).prefix() {
39 Some(prefix) => {
40 prefix.hash(h);
41 (prefix.len(), prefix.kind().is_verbatim())
42 }
43 None => (0, false),
44 };
45 let bytes = &path[prefix_len..];
46
47 let mut component_start = 0;
48 let mut bytes_hashed = 0;
49
50 for i in 0..bytes.len() {
51 let is_sep = if verbatim {
52 path[i] == SEPARATOR as u8
53 } else {
54 path[i] == SEPARATOR as u8 || path[i] == ALT_SEPARATOR as u8
55 };
56 if is_sep {
57 if i > component_start {
58 let to_hash = &bytes[component_start..i];
59 h.write(to_hash);
60 bytes_hashed += to_hash.len();
61 }
62
63 component_start = i + 1;
66
67 let tail = &bytes[component_start..];
68
69 if !verbatim {
70 component_start += match tail {
71 [b'.'] => 1,
72 [b'.', sep, ..]
73 if *sep == SEPARATOR as u8 || *sep == ALT_SEPARATOR as u8 =>
74 {
75 1
76 }
77 _ => 0,
78 };
79 }
80 }
81 }
82
83 if component_start < bytes.len() {
84 let to_hash = &bytes[component_start..];
85 h.write(to_hash);
86 bytes_hashed += to_hash.len();
87 }
88
89 h.write_usize(bytes_hashed);
90 }
91
92 fn push(current_path: &mut Vec<u8>, path: &[u8]) {
130 if path.is_empty() {
131 return;
132 }
133
134 let comps = Self::components(path);
135 let cur_comps = Self::components(current_path);
136
137 if comps.is_absolute() || comps.has_prefix() {
138 current_path.clear();
139 current_path.extend_from_slice(path);
140 } else if cur_comps.has_any_verbatim_prefix() && !path.is_empty() {
141 let mut buffer: Vec<_> = Self::components(current_path).collect();
142 for c in Self::components(path) {
143 match c {
144 WindowsComponent::RootDir => {
145 buffer.truncate(1);
146 buffer.push(c);
147 }
148 WindowsComponent::CurDir => (),
149 WindowsComponent::ParentDir => {
150 if let Some(WindowsComponent::Normal(_)) = buffer.last() {
151 buffer.pop();
152 }
153 }
154 _ => buffer.push(c),
155 }
156 }
157
158 let mut new_path = Vec::new();
159 let mut need_sep = false;
160
161 for c in buffer {
162 if need_sep && c != WindowsComponent::RootDir {
163 new_path.push(SEPARATOR as u8);
164 }
165
166 new_path.extend_from_slice(c.as_bytes());
167
168 need_sep = match c {
169 WindowsComponent::RootDir => false,
170 WindowsComponent::Prefix(prefix) => {
171 !matches!(prefix.kind(), WindowsPrefix::Disk(_))
172 }
173 _ => true,
174 };
175 }
176
177 *current_path = new_path;
178 } else if comps.has_root() {
179 let len = Self::components(current_path).prefix_len();
180 current_path.truncate(len);
181 current_path.extend_from_slice(path);
182 } else {
183 let needs_sep = (!current_path.is_empty()
186 && !current_path.ends_with(&[SEPARATOR as u8]))
187 && !Self::components(current_path).is_only_disk();
188
189 if needs_sep {
190 current_path.push(SEPARATOR as u8);
191 }
192
193 current_path.extend_from_slice(path);
194 }
195 }
196
197 fn push_checked(current_path: &mut Vec<u8>, path: &[u8]) -> Result<(), CheckedPathError> {
198 let mut normal_cnt = 0;
203 for component in WindowsPath::new(path).components() {
204 match component {
205 WindowsComponent::Prefix(_) => return Err(CheckedPathError::UnexpectedPrefix),
206 WindowsComponent::RootDir => return Err(CheckedPathError::UnexpectedRoot),
207 WindowsComponent::ParentDir if normal_cnt == 0 => {
208 return Err(CheckedPathError::PathTraversalAttack)
209 }
210 WindowsComponent::ParentDir => normal_cnt -= 1,
211 WindowsComponent::Normal(bytes) => {
212 for b in bytes {
213 if DISALLOWED_FILENAME_BYTES.contains(b) {
214 return Err(CheckedPathError::InvalidFilename);
215 }
216 }
217 normal_cnt += 1;
218 }
219 _ => continue,
220 }
221 }
222
223 Self::push(current_path, path);
224 Ok(())
225 }
226}
227
228impl fmt::Debug for WindowsEncoding {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 f.debug_struct("WindowsEncoding").finish()
231 }
232}
233
234impl fmt::Display for WindowsEncoding {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 write!(f, "WindowsEncoding")
237 }
238}
239
240impl<T> Path<T>
241where
242 T: Encoding,
243{
244 pub fn has_windows_encoding(&self) -> bool {
255 T::label() == WindowsEncoding::label()
256 }
257
258 pub fn with_windows_encoding(&self) -> PathBuf<WindowsEncoding> {
262 self.with_encoding()
263 }
264
265 pub fn with_windows_encoding_checked(
270 &self,
271 ) -> Result<PathBuf<WindowsEncoding>, CheckedPathError> {
272 self.with_encoding_checked()
273 }
274}
275
276impl WindowsPath {
277 pub fn to_typed_path(&self) -> TypedPath<'_> {
278 TypedPath::windows(self)
279 }
280
281 pub fn to_typed_path_buf(&self) -> TypedPathBuf {
282 TypedPathBuf::from_windows(self)
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn push_checked_should_fail_if_providing_an_absolute_path() {
292 let mut current_path = vec![];
294 assert_eq!(
295 WindowsEncoding::push_checked(&mut current_path, br"\abc"),
296 Err(CheckedPathError::UnexpectedRoot)
297 );
298 assert_eq!(current_path, b"");
299
300 let mut current_path = br"some\path".to_vec();
302 assert_eq!(
303 WindowsEncoding::push_checked(&mut current_path, br"\abc"),
304 Err(CheckedPathError::UnexpectedRoot)
305 );
306 assert_eq!(current_path, br"some\path");
307
308 let mut current_path = br"\some\path\".to_vec();
310 assert_eq!(
311 WindowsEncoding::push_checked(&mut current_path, br"\abc"),
312 Err(CheckedPathError::UnexpectedRoot)
313 );
314 assert_eq!(current_path, br"\some\path\");
315 }
316
317 #[test]
318 fn push_checked_should_fail_if_providing_a_path_with_an_embedded_prefix() {
319 let mut current_path = vec![];
321 assert_eq!(
322 WindowsEncoding::push_checked(&mut current_path, br"C:abc"),
323 Err(CheckedPathError::UnexpectedPrefix)
324 );
325 assert_eq!(current_path, b"");
326
327 let mut current_path = br"some\path".to_vec();
329 assert_eq!(
330 WindowsEncoding::push_checked(&mut current_path, br"C:abc"),
331 Err(CheckedPathError::UnexpectedPrefix)
332 );
333 assert_eq!(current_path, br"some\path");
334
335 let mut current_path = br"\some\path\".to_vec();
337 assert_eq!(
338 WindowsEncoding::push_checked(&mut current_path, br"C:abc"),
339 Err(CheckedPathError::UnexpectedPrefix)
340 );
341 assert_eq!(current_path, br"\some\path\");
342 }
343
344 #[test]
345 fn push_checked_should_fail_if_providing_a_path_with_disallowed_filename_bytes() {
346 let mut current_path = vec![];
348 assert_eq!(
349 WindowsEncoding::push_checked(&mut current_path, br"some\inva|lid\path"),
350 Err(CheckedPathError::InvalidFilename)
351 );
352 assert_eq!(current_path, b"");
353
354 let mut current_path = br"some\path".to_vec();
357 assert_eq!(
358 WindowsEncoding::push_checked(&mut current_path, br"some\inva|lid\path"),
359 Err(CheckedPathError::InvalidFilename)
360 );
361 assert_eq!(current_path, br"some\path");
362
363 let mut current_path = br"\some\path\".to_vec();
366 assert_eq!(
367 WindowsEncoding::push_checked(&mut current_path, br"some\inva|lid\path"),
368 Err(CheckedPathError::InvalidFilename)
369 );
370 assert_eq!(current_path, br"\some\path\");
371 }
372
373 #[test]
374 fn push_checked_should_fail_if_providing_a_path_that_would_escape_the_current_path() {
375 let mut current_path = vec![];
377 assert_eq!(
378 WindowsEncoding::push_checked(&mut current_path, b".."),
379 Err(CheckedPathError::PathTraversalAttack)
380 );
381 assert_eq!(current_path, b"");
382
383 let mut current_path = br"some\path".to_vec();
385 assert_eq!(
386 WindowsEncoding::push_checked(&mut current_path, b".."),
387 Err(CheckedPathError::PathTraversalAttack)
388 );
389 assert_eq!(current_path, br"some\path");
390
391 let mut current_path = br"\some\path\".to_vec();
393 assert_eq!(
394 WindowsEncoding::push_checked(&mut current_path, b".."),
395 Err(CheckedPathError::PathTraversalAttack)
396 );
397 assert_eq!(current_path, br"\some\path\");
398 }
399
400 #[test]
401 fn push_checked_should_append_path_to_current_path_with_a_separator_if_does_not_violate_rules()
402 {
403 let mut current_path = vec![];
406 assert_eq!(
407 WindowsEncoding::push_checked(&mut current_path, br"abc\..\def\."),
408 Ok(()),
409 );
410 assert_eq!(current_path, br"abc\..\def\.");
411
412 let mut current_path = br"some\path".to_vec();
413 assert_eq!(
414 WindowsEncoding::push_checked(&mut current_path, br"abc\..\def\."),
415 Ok(()),
416 );
417 assert_eq!(current_path, br"some\path\abc\..\def\.");
418
419 let mut current_path = br"\some\path\".to_vec();
420 assert_eq!(
421 WindowsEncoding::push_checked(&mut current_path, br"abc\..\def\."),
422 Ok(()),
423 );
424 assert_eq!(current_path, br"\some\path\abc\..\def\.");
425 }
426}