// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::path::Component; use std::path::Path; use std::path::PathBuf; use url::Url; /// Extension to path_clean::PathClean pub trait PathClean { fn clean(&self) -> T; } impl PathClean for PathBuf { fn clean(&self) -> PathBuf { fn is_clean_path(path: &Path) -> bool { let path = path.to_string_lossy(); let mut current_index = 0; while let Some(index) = path[current_index..].find("\\.") { let trailing_index = index + current_index + 2; let mut trailing_chars = path[trailing_index..].chars(); match trailing_chars.next() { Some('.') => match trailing_chars.next() { Some('/') | Some('\\') | None => { return false; } _ => {} }, Some('/') | Some('\\') => { return false; } _ => {} } current_index = trailing_index; } true } let path = path_clean::PathClean::clean(self); if cfg!(windows) && !is_clean_path(&path) { // temporary workaround because path_clean::PathClean::clean is // not good enough on windows let mut components = Vec::new(); for component in path.components() { match component { Component::CurDir => { // skip } Component::ParentDir => { let maybe_last_component = components.pop(); if !matches!(maybe_last_component, Some(Component::Normal(_))) { panic!("Error normalizing: {}", path.display()); } } Component::Normal(_) | Component::RootDir | Component::Prefix(_) => { components.push(component); } } } components.into_iter().collect::() } else { path } } } pub(crate) fn to_file_specifier(path: &Path) -> Url { match Url::from_file_path(path) { Ok(url) => url, Err(_) => panic!("Invalid path: {}", path.display()), } } // todo(dsherret): we have the below code also in deno_core and it // would be good to somehow re-use it in both places (we don't want // to create a dependency on deno_core here) #[cfg(not(windows))] #[inline] pub fn strip_unc_prefix(path: PathBuf) -> PathBuf { path } /// Strips the unc prefix (ex. \\?\) from Windows paths. #[cfg(windows)] pub fn strip_unc_prefix(path: PathBuf) -> PathBuf { use std::path::Component; use std::path::Prefix; let mut components = path.components(); match components.next() { Some(Component::Prefix(prefix)) => { match prefix.kind() { // \\?\device Prefix::Verbatim(device) => { let mut path = PathBuf::new(); path.push(format!(r"\\{}\", device.to_string_lossy())); path.extend(components.filter(|c| !matches!(c, Component::RootDir))); path } // \\?\c:\path Prefix::VerbatimDisk(_) => { let mut path = PathBuf::new(); path.push(prefix.as_os_str().to_string_lossy().replace(r"\\?\", "")); path.extend(components); path } // \\?\UNC\hostname\share_name\path Prefix::VerbatimUNC(hostname, share_name) => { let mut path = PathBuf::new(); path.push(format!( r"\\{}\{}\", hostname.to_string_lossy(), share_name.to_string_lossy() )); path.extend(components.filter(|c| !matches!(c, Component::RootDir))); path } _ => path, } } _ => path, } } #[cfg(test)] mod test { #[cfg(windows)] #[test] fn test_path_clean() { use super::*; run_test("C:\\test\\./file.txt", "C:\\test\\file.txt"); run_test("C:\\test\\../other/file.txt", "C:\\other\\file.txt"); run_test("C:\\test\\../other\\file.txt", "C:\\other\\file.txt"); fn run_test(input: &str, expected: &str) { assert_eq!(PathBuf::from(input).clean(), PathBuf::from(expected)); } } #[cfg(windows)] #[test] fn test_strip_unc_prefix() { use std::path::PathBuf; run_test(r"C:\", r"C:\"); run_test(r"C:\test\file.txt", r"C:\test\file.txt"); run_test(r"\\?\C:\", r"C:\"); run_test(r"\\?\C:\test\file.txt", r"C:\test\file.txt"); run_test(r"\\.\C:\", r"\\.\C:\"); run_test(r"\\.\C:\Test\file.txt", r"\\.\C:\Test\file.txt"); run_test(r"\\?\UNC\localhost\", r"\\localhost"); run_test(r"\\?\UNC\localhost\c$\", r"\\localhost\c$"); run_test( r"\\?\UNC\localhost\c$\Windows\file.txt", r"\\localhost\c$\Windows\file.txt", ); run_test(r"\\?\UNC\wsl$\deno.json", r"\\wsl$\deno.json"); run_test(r"\\?\server1", r"\\server1"); run_test(r"\\?\server1\e$\", r"\\server1\e$\"); run_test( r"\\?\server1\e$\test\file.txt", r"\\server1\e$\test\file.txt", ); fn run_test(input: &str, expected: &str) { assert_eq!( super::strip_unc_prefix(PathBuf::from(input)), PathBuf::from(expected) ); } } }