2024-07-25 19:08:14 -04:00
|
|
|
// 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<T> {
|
|
|
|
fn clean(&self) -> T;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PathClean<PathBuf> for PathBuf {
|
|
|
|
fn clean(&self) -> PathBuf {
|
2024-07-30 16:46:15 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-07-25 19:08:14 -04:00
|
|
|
let path = path_clean::PathClean::clean(self);
|
2024-07-30 16:46:15 -04:00
|
|
|
if cfg!(windows) && !is_clean_path(&path) {
|
2024-07-25 19:08:14 -04:00
|
|
|
// 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::<PathBuf>()
|
|
|
|
} 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)]
|
2024-07-30 16:46:15 -04:00
|
|
|
#[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)]
|
2024-07-25 19:08:14 -04:00
|
|
|
#[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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|