summaryrefslogtreecommitdiff
path: root/resolvers/node/path.rs
diff options
context:
space:
mode:
Diffstat (limited to 'resolvers/node/path.rs')
-rw-r--r--resolvers/node/path.rs179
1 files changed, 179 insertions, 0 deletions
diff --git a/resolvers/node/path.rs b/resolvers/node/path.rs
new file mode 100644
index 000000000..ece270cd9
--- /dev/null
+++ b/resolvers/node/path.rs
@@ -0,0 +1,179 @@
+// 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 {
+ 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::<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)]
+ #[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)
+ );
+ }
+ }
+}