diff --git a/Cargo.lock b/Cargo.lock index e75464580c..463e9cdd64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1850,9 +1850,9 @@ dependencies = [ [[package]] name = "deno_doc" -version = "0.175.0" +version = "0.176.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161ec3cf8d07fc08a24a2600586881d9abafd48a0cb778633883dc8c1665d6fe" +checksum = "95ce3b4ff0fee9bd2f2c73996739556d986e1486cdc74e44da2b8f3296ea28bb" dependencies = [ "anyhow", "cfg-if", @@ -2000,9 +2000,9 @@ dependencies = [ [[package]] name = "deno_graph" -version = "0.92.0" +version = "0.93.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51fed59054a55f3cbd0db096168c58836ca9f94c7b7c1b0730f63064d7dc0f9e" +checksum = "8f7b63c5334fd2ee41db14596a5b03c37ce90dff057c1da9f573de515a3bb2c0" dependencies = [ "async-trait", "capacity_builder", @@ -2529,15 +2529,18 @@ dependencies = [ "fqdn", "libc", "log", + "nix 0.27.1", "once_cell", "parking_lot", "percent-encoding", "serde", "serde_json", + "sys_traits", + "temp_deno_which", "thiserror 2.0.12", "url", - "which", "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -2559,10 +2562,10 @@ dependencies = [ "rand", "serde", "simd-json", + "sys_traits", "tempfile", "thiserror 2.0.12", "tokio", - "which", "winapi", "windows-sys 0.59.0", ] @@ -3571,9 +3574,9 @@ checksum = "31ae425815400e5ed474178a7a22e275a9687086a12ca63ec793ff292d8fdae8" [[package]] name = "eszip" -version = "0.89.0" +version = "0.90.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d9ea60ba198add01557b0f9e4d4df3795bbdc58a4c6774fee46734accdd64d" +checksum = "c5a6b90a53591e5adf9884048a79c291305b2a576d69fbc2b85de31d1a6e8372" dependencies = [ "anyhow", "async-trait", @@ -8398,9 +8401,9 @@ dependencies = [ [[package]] name = "sys_traits" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db83fa685d1637625266341c69bf6e810632f5a53502d5d03fde8fcbcd3153f2" +checksum = "b0f8c2c55b6b4dd67f0f8df8de9bdf00b16c8ea4fbc4be0c2133d5d3924be5d4" dependencies = [ "filetime", "getrandom", @@ -8452,6 +8455,15 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +[[package]] +name = "temp_deno_which" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366c5ccd670145885feb6efd6bbf2478ed236c4c3839046fcc8e2a1a84c51091" +dependencies = [ + "either", +] + [[package]] name = "tempfile" version = "3.10.1" diff --git a/Cargo.toml b/Cargo.toml index bd00401c18..62c777769d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,9 +59,9 @@ deno_core = { version = "0.348.0" } deno_cache_dir = "=0.21.0" deno_config = { version = "=0.54.2", features = ["workspace"] } -deno_doc = "=0.175.0" +deno_doc = "=0.176.0" deno_error = "=0.5.7" -deno_graph = { version = "=0.92.0", default-features = false } +deno_graph = { version = "=0.93.0", default-features = false } deno_lint = "=0.75.0" deno_lockfile = "=0.28.0" deno_media_type = { version = "=0.2.8", features = ["module_specifier"] } @@ -74,7 +74,7 @@ deno_task_shell = "=0.23.0" deno_terminal = "=0.2.2" deno_unsync = "0.4.3" deno_whoami = "0.1.0" -eszip = "=0.89.0" +eszip = "=0.90.0" denokv_proto = "0.10.0" denokv_remote = "0.10.0" @@ -237,8 +237,10 @@ simd-json = "0.14.0" slab = "0.4" smallvec = "1.8" socket2 = { version = "0.5.3", features = ["all"] } -sys_traits = "=0.1.12" +sys_traits = "=0.1.14" tar = "=0.4.43" +# temporarily using until https://github.com/harryfei/which-rs/pull/109 is released +temp_deno_which = { version = "0.1.0", default-features = false } tempfile = "3.4.0" termcolor = "1.1.3" thiserror = "2.0.12" @@ -265,7 +267,6 @@ weak-table = "0.3.2" web-transport-proto = "0.2.3" webpki-root-certs = "0.26.5" webpki-roots = "0.26" -which = "6" yoke = { version = "0.7.4", features = ["derive"] } zeromq = { version = "=0.4.1", default-features = false, features = ["tcp-transport", "tokio-runtime"] } zip = { version = "2.4.1", default-features = false, features = ["flate2"] } diff --git a/cli/cache/mod.rs b/cli/cache/mod.rs index 2910781f92..39809c99d3 100644 --- a/cli/cache/mod.rs +++ b/cli/cache/mod.rs @@ -4,7 +4,6 @@ use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; -use deno_ast::MediaType; use deno_cache_dir::file_fetcher::CacheSetting; use deno_cache_dir::file_fetcher::FetchLocalOptions; use deno_cache_dir::file_fetcher::FetchNoFollowErrorKind; @@ -75,7 +74,6 @@ pub struct FetchCacher { file_fetcher: Arc, global_http_cache: Arc, in_npm_pkg_checker: DenoInNpmPackageChecker, - module_info_cache: Arc, permissions: PermissionsContainer, sys: CliSys, is_deno_publish: bool, @@ -87,7 +85,6 @@ impl FetchCacher { file_fetcher: Arc, global_http_cache: Arc, in_npm_pkg_checker: DenoInNpmPackageChecker, - module_info_cache: Arc, sys: CliSys, options: FetchCacherOptions, ) -> Self { @@ -95,7 +92,6 @@ impl FetchCacher { file_fetcher, global_http_cache, in_npm_pkg_checker, - module_info_cache, sys, file_header_overrides: options.file_header_overrides, permissions: options.permissions, @@ -287,28 +283,4 @@ impl Loader for FetchCacher { } .boxed_local() } - - fn cache_module_info( - &self, - specifier: &ModuleSpecifier, - media_type: MediaType, - source: &Arc<[u8]>, - module_info: &deno_graph::ModuleInfo, - ) { - log::debug!("Caching module info for {}", specifier); - let source_hash = CacheDBHash::from_hashable(source); - let result = self.module_info_cache.set_module_info( - specifier, - media_type, - source_hash, - module_info, - ); - if let Err(err) = result { - log::debug!( - "Error saving module cache info for {}. {:#}", - specifier, - err - ); - } - } } diff --git a/cli/cache/module_info.rs b/cli/cache/module_info.rs index 018abbea0f..722e7b0f5b 100644 --- a/cli/cache/module_info.rs +++ b/cli/cache/module_info.rs @@ -136,6 +136,28 @@ impl ModuleInfoCache { } } +impl deno_graph::source::ModuleInfoCacher for ModuleInfoCache { + fn cache_module_info( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + source: &Arc<[u8]>, + module_info: &deno_graph::ModuleInfo, + ) { + log::debug!("Caching module info for {}", specifier); + let source_hash = CacheDBHash::from_hashable(source); + let result = + self.set_module_info(specifier, media_type, source_hash, module_info); + if let Err(err) = result { + log::debug!( + "Error saving module cache info for {}. {:#}", + specifier, + err + ); + } + } +} + pub struct ModuleInfoCacheModuleAnalyzer<'a> { module_info_cache: &'a ModuleInfoCache, parsed_source_cache: &'a Arc, diff --git a/cli/graph_util.rs b/cli/graph_util.rs index 4a0fdf5fd9..372ac0c1af 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -816,6 +816,7 @@ impl ModuleGraphBuilder { jsr_url_provider: &CliJsrUrlProvider, npm_resolver: Some(self.npm_graph_resolver.as_ref()), module_analyzer: &analyzer, + module_info_cacher: self.module_info_cache.as_ref(), reporter: maybe_file_watcher_reporter, resolver: Some(&graph_resolver), locker: locker.as_mut().map(|l| l as _), @@ -998,7 +999,6 @@ impl ModuleGraphBuilder { self.file_fetcher.clone(), self.global_http_cache.clone(), self.in_npm_pkg_checker.clone(), - self.module_info_cache.clone(), self.sys.clone(), cache::FetchCacherOptions { file_header_overrides: self.cli_options.resolve_file_header_overrides(), diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index d8dd995120..161d69ff2a 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -1899,21 +1899,6 @@ impl deno_graph::source::Loader for OpenDocumentsGraphLoader<'_> { None => self.inner_loader.load(specifier, options), } } - - fn cache_module_info( - &self, - specifier: &deno_ast::ModuleSpecifier, - media_type: MediaType, - source: &Arc<[u8]>, - module_info: &deno_graph::ModuleInfo, - ) { - self.inner_loader.cache_module_info( - specifier, - media_type, - source, - module_info, - ) - } } fn parse_and_analyze_module( diff --git a/cli/rt/file_system.rs b/cli/rt/file_system.rs index b22cb5eed6..075beab6aa 100644 --- a/cli/rt/file_system.rs +++ b/cli/rt/file_system.rs @@ -582,7 +582,7 @@ impl sys_traits::BaseFsReadDir for DenoRtSys { &self, path: &Path, ) -> std::io::Result< - Box> + '_>, + Box>>, > { if self.0.is_path_within(path) { let entries = self.0.read_dir_with_metadata(path)?; @@ -905,12 +905,21 @@ impl sys_traits::ThreadSleep for DenoRtSys { } impl sys_traits::EnvCurrentDir for DenoRtSys { + #[inline] fn env_current_dir(&self) -> std::io::Result { #[allow(clippy::disallowed_types)] // ok because we're implementing the fs sys_traits::impls::RealSys.env_current_dir() } } +impl sys_traits::EnvHomeDir for DenoRtSys { + #[inline] + fn env_home_dir(&self) -> Option { + #[allow(clippy::disallowed_types)] // ok because we're implementing the fs + sys_traits::impls::RealSys.env_home_dir() + } +} + impl sys_traits::BaseEnvVar for DenoRtSys { fn base_env_var_os( &self, @@ -1381,16 +1390,23 @@ impl FileBackedVfs { ) } - pub fn read_dir_with_metadata<'a>( - &'a self, + pub fn read_dir_with_metadata( + &self, path: &Path, - ) -> std::io::Result + 'a> { + ) -> std::io::Result> { let dir = self.dir_entry(path)?; let path = path.to_path_buf(); - Ok(dir.entries.iter().map(move |entry| FileBackedVfsDirEntry { - parent_path: path.to_path_buf(), - metadata: FileBackedVfsMetadata::from_vfs_entry_ref(entry.as_ref()), - })) + Ok( + dir + .entries + .iter() + .map(move |entry| FileBackedVfsDirEntry { + parent_path: path.to_path_buf(), + metadata: FileBackedVfsMetadata::from_vfs_entry_ref(entry.as_ref()), + }) + .collect::>() + .into_iter(), + ) } pub fn read_link(&self, path: &Path) -> std::io::Result { diff --git a/cli/tools/doc.rs b/cli/tools/doc.rs index 36b34d2d4e..3b70268358 100644 --- a/cli/tools/doc.rs +++ b/cli/tools/doc.rs @@ -81,6 +81,7 @@ async fn generate_doc_nodes_for_builtin_types( jsr_url_provider: Default::default(), locker: None, module_analyzer: analyzer, + module_info_cacher: Default::default(), npm_resolver: None, reporter: None, resolver: None, diff --git a/ext/process/Cargo.toml b/ext/process/Cargo.toml index 7603c2dc5f..8620c4b792 100644 --- a/ext/process/Cargo.toml +++ b/ext/process/Cargo.toml @@ -28,10 +28,10 @@ pin-project-lite.workspace = true rand.workspace = true serde.workspace = true simd-json.workspace = true +sys_traits = { workspace = true, features = ["real", "winapi", "libc"] } tempfile.workspace = true thiserror.workspace = true tokio.workspace = true -which.workspace = true [target.'cfg(unix)'.dependencies] nix = { workspace = true, features = ["signal", "process"] } diff --git a/ext/process/lib.rs b/ext/process/lib.rs index 7a0f712808..7d7dc18861 100644 --- a/ext/process/lib.rs +++ b/ext/process/lib.rs @@ -254,7 +254,7 @@ pub enum ProcessError { BorrowMut(std::cell::BorrowMutError), #[class(generic)] #[error(transparent)] - Which(which::Error), + Which(deno_permissions::which::Error), #[class(type)] #[error("Child process has already terminated.")] ChildProcessAlreadyTerminated, @@ -800,9 +800,14 @@ fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result { Ok(resolve_path(cmd, &env.cwd)) } else { let path = env.envs.get(&EnvVarKey::new(OsString::from("PATH"))); - match which::which_in(cmd, path, &env.cwd) { + match deno_permissions::which::which_in( + sys_traits::impls::RealSys, + cmd, + path.cloned(), + env.cwd.clone(), + ) { Ok(cmd) => Ok(cmd), - Err(which::Error::CannotFindBinaryPath) => { + Err(deno_permissions::which::Error::CannotFindBinaryPath) => { Err(std::io::Error::from(std::io::ErrorKind::NotFound).into()) } Err(err) => Err(ProcessError::Which(err)), diff --git a/resolvers/node/cache.rs b/resolvers/node/cache.rs index a5542742d4..b96f9a7e74 100644 --- a/resolvers/node/cache.rs +++ b/resolvers/node/cache.rs @@ -180,7 +180,7 @@ impl BaseFsReadDir for NodeResolutionSys { &self, path: &Path, ) -> std::io::Result< - Box> + '_>, + Box>>, > { self.sys.base_fs_read_dir(path) } diff --git a/runtime/permissions.rs b/runtime/permissions.rs index 8b65c6b1b0..705ec82004 100644 --- a/runtime/permissions.rs +++ b/runtime/permissions.rs @@ -22,12 +22,12 @@ use deno_permissions::WriteDescriptor; #[derive(Debug)] pub struct RuntimePermissionDescriptorParser< - TSys: sys_traits::EnvCurrentDir + Send + Sync, + TSys: deno_permissions::which::WhichSys + Send + Sync, > { sys: TSys, } -impl +impl RuntimePermissionDescriptorParser { pub fn new(sys: TSys) -> Self { @@ -55,7 +55,7 @@ impl } } -impl +impl deno_permissions::PermissionDescriptorParser for RuntimePermissionDescriptorParser { @@ -113,7 +113,11 @@ impl &self, text: &str, ) -> Result { - Ok(AllowRunDescriptor::parse(text, &self.resolve_cwd()?)?) + Ok(AllowRunDescriptor::parse( + text, + &self.resolve_cwd()?, + &self.sys, + )?) } fn parse_deny_run_descriptor( @@ -149,7 +153,7 @@ impl if requested.is_empty() { return Err(RunDescriptorParseError::EmptyRunQuery); } - RunQueryDescriptor::parse(requested) + RunQueryDescriptor::parse(requested, &self.sys) .map_err(RunDescriptorParseError::PathResolve) } } diff --git a/runtime/permissions/Cargo.toml b/runtime/permissions/Cargo.toml index b37e1dc25a..1492478986 100644 --- a/runtime/permissions/Cargo.toml +++ b/runtime/permissions/Cargo.toml @@ -27,9 +27,17 @@ parking_lot.workspace = true percent-encoding = { workspace = true, features = [] } serde.workspace = true serde_json.workspace = true +sys_traits.workspace = true +temp_deno_which.workspace = true thiserror.workspace = true url.workspace = true -which.workspace = true [target.'cfg(windows)'.dependencies] winapi = { workspace = true, features = ["commapi", "knownfolders", "mswsock", "objbase", "psapi", "shlobj", "tlhelp32", "winbase", "winerror", "winuser", "winsock2", "processenv", "wincon", "wincontypes"] } +windows-sys = { workspace = true, features = ["Win32_Storage_FileSystem"] } + +[target.'cfg(unix)'.dependencies] +nix = { workspace = true } + +[dev-dependencies] +sys_traits = { workspace = true, features = ["real"] } diff --git a/runtime/permissions/clippy.toml b/runtime/permissions/clippy.toml index b5ed912cce..d2c94a63a3 100644 --- a/runtime/permissions/clippy.toml +++ b/runtime/permissions/clippy.toml @@ -1,3 +1,49 @@ disallowed-methods = [ { path = "std::str::FromStr", reason = "Don't want to have stuff like `'0.0.0.0'.parse().unwrap()`. Instead implement ConcreteType::parse methods." }, + { path = "std::env::current_dir", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::canonicalize", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::is_dir", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::is_file", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::is_symlink", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::metadata", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::read_dir", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::read_link", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::symlink_metadata", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::try_exists", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::PathBuf::exists", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::PathBuf::canonicalize", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::PathBuf::is_dir", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::PathBuf::is_file", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::PathBuf::is_symlink", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::PathBuf::metadata", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::PathBuf::read_dir", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::PathBuf::read_link", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::PathBuf::symlink_metadata", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::PathBuf::try_exists", reason = "File system operations should be done using sys_traits" }, + { path = "std::env::set_current_dir", reason = "File system operations should be done using sys_traits" }, + { path = "std::env::temp_dir", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::canonicalize", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::copy", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::create_dir_all", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::create_dir", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::DirBuilder::new", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::hard_link", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::metadata", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::OpenOptions::new", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::read_dir", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::read_link", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::read_to_string", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::read", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::remove_dir_all", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::remove_dir", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::remove_file", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::rename", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::set_permissions", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::symlink_metadata", reason = "File system operations should be done using sys_traits" }, + { path = "std::fs::write", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::canonicalize", reason = "File system operations should be done using sys_traits" }, + { path = "std::path::Path::exists", reason = "File system operations should be done using sys_traits" }, + { path = "url::Url::to_file_path", reason = "Use deno_path_util instead so it works in Wasm" }, + { path = "url::Url::from_file_path", reason = "Use deno_path_util instead so it works in Wasm" }, + { path = "url::Url::from_directory_path", reason = "Use deno_path_util instead so it works in Wasm" }, ] diff --git a/runtime/permissions/lib.rs b/runtime/permissions/lib.rs index 2294f74f98..d4d9cc1ded 100644 --- a/runtime/permissions/lib.rs +++ b/runtime/permissions/lib.rs @@ -28,15 +28,19 @@ use serde::Serialize; use url::Url; pub mod prompter; +pub mod which; use prompter::permission_prompt; pub use prompter::set_prompt_callbacks; pub use prompter::set_prompter; +pub use prompter::DeniedPrompter; pub use prompter::GetFormattedStackFn; pub use prompter::PermissionPrompter; pub use prompter::PromptCallback; pub use prompter::PromptResponse; use prompter::PERMISSION_EMOJI; +use self::which::WhichSys; + #[derive(Debug, thiserror::Error)] pub enum PermissionDeniedError { #[error("Requires {access}, {}", format_permission_error(.name))] @@ -1373,14 +1377,16 @@ pub enum PathResolveError { impl RunQueryDescriptor { pub fn parse( requested: &str, + sys: &impl which::WhichSys, ) -> Result { if is_path(requested) { let path = PathBuf::from(requested); let resolved = if path.is_absolute() { normalize_path(path) } else { - let cwd = - std::env::current_dir().map_err(PathResolveError::CwdResolve)?; + let cwd = sys + .env_current_dir() + .map_err(PathResolveError::CwdResolve)?; normalize_path(cwd.join(path)) }; Ok(RunQueryDescriptor::Path { @@ -1388,7 +1394,11 @@ impl RunQueryDescriptor { resolved, }) } else { - match which::which(requested) { + let cwd = sys + .env_current_dir() + .map_err(PathResolveError::CwdResolve)?; + match which::which_in(sys.clone(), requested, sys.env_var_os("PATH"), cwd) + { Ok(resolved) => Ok(RunQueryDescriptor::Path { requested: requested.to_string(), resolved, @@ -1542,13 +1552,19 @@ impl AllowRunDescriptor { pub fn parse( text: &str, cwd: &Path, + sys: &impl WhichSys, ) -> Result { let is_path = is_path(text); // todo(dsherret): canonicalize in #25458 let path = if is_path { resolve_from_known_cwd(Path::new(text), cwd) } else { - match which::which_in(text, std::env::var_os("PATH"), cwd) { + match which::which_in( + sys.clone(), + text, + sys.env_var_os("PATH"), + cwd.to_path_buf(), + ) { Ok(path) => path, Err(err) => match err { which::Error::CannotGetCurrentDirAndPathListEmpty => { @@ -3879,7 +3895,8 @@ mod tests { &self, requested: &str, ) -> Result { - RunQueryDescriptor::parse(requested).map_err(Into::into) + RunQueryDescriptor::parse(requested, &sys_traits::impls::RealSys) + .map_err(Into::into) } } diff --git a/runtime/permissions/prompter.rs b/runtime/permissions/prompter.rs index f7e6e84a1f..67550f036a 100644 --- a/runtime/permissions/prompter.rs +++ b/runtime/permissions/prompter.rs @@ -1,20 +1,13 @@ // Copyright 2018-2025 the Deno authors. MIT license. -use std::fmt::Write; -use std::io::BufRead; -use std::io::IsTerminal; -use std::io::StderrLock; -use std::io::StdinLock; -use std::io::Write as IoWrite; - -use deno_terminal::colors; use once_cell::sync::Lazy; use parking_lot::Mutex; -use crate::is_standalone; - /// Helper function to make control characters visible so users can see the underlying filename. +#[cfg(not(target_arch = "wasm32"))] fn escape_control_characters(s: &str) -> std::borrow::Cow { + use deno_terminal::colors; + if !s.contains(|c: char| c.is_ascii_control() || c.is_control()) { return std::borrow::Cow::Borrowed(s); } @@ -35,9 +28,6 @@ fn escape_control_characters(s: &str) -> std::borrow::Cow { pub const PERMISSION_EMOJI: &str = "⚠️"; -// 10kB of permission prompting should be enough for anyone -const MAX_PERMISSION_PROMPT_LENGTH: usize = 10 * 1024; - #[derive(Debug, Eq, PartialEq)] pub enum PromptResponse { Allow, @@ -45,8 +35,13 @@ pub enum PromptResponse { AllowAll, } +#[cfg(not(target_arch = "wasm32"))] +type DefaultPrompter = TtyPrompter; +#[cfg(target_arch = "wasm32")] +type DefaultPrompter = DeniedPrompter; + static PERMISSION_PROMPTER: Lazy>> = - Lazy::new(|| Mutex::new(Box::new(TtyPrompter))); + Lazy::new(|| Mutex::new(Box::new(DefaultPrompter::default()))); static MAYBE_BEFORE_PROMPT_CALLBACK: Lazy>> = Lazy::new(|| Mutex::new(None)); @@ -107,12 +102,26 @@ pub trait PermissionPrompter: Send + Sync { ) -> PromptResponse; } -pub struct TtyPrompter; +#[derive(Default)] +pub struct DeniedPrompter; + +impl PermissionPrompter for DeniedPrompter { + fn prompt( + &mut self, + _message: &str, + _name: &str, + _api_name: Option<&str>, + _is_unary: bool, + _get_stack: Option, + ) -> PromptResponse { + PromptResponse::Deny + } +} #[cfg(unix)] fn clear_stdin( - _stdin_lock: &mut StdinLock, - _stderr_lock: &mut StderrLock, + _stdin_lock: &mut std::io::StdinLock, + _stderr_lock: &mut std::io::StderrLock, ) -> Result<(), std::io::Error> { use std::mem::MaybeUninit; @@ -163,11 +172,15 @@ fn clear_stdin( Ok(()) } -#[cfg(not(unix))] +#[cfg(all(not(unix), not(target_arch = "wasm32")))] fn clear_stdin( - stdin_lock: &mut StdinLock, - stderr_lock: &mut StderrLock, + stdin_lock: &mut std::io::StdinLock, + stderr_lock: &mut std::io::StderrLock, ) -> Result<(), std::io::Error> { + use std::io::BufRead; + use std::io::StdinLock; + use std::io::Write as IoWrite; + use winapi::shared::minwindef::TRUE; use winapi::shared::minwindef::UINT; use winapi::shared::minwindef::WORD; @@ -257,7 +270,7 @@ fn clear_stdin( } fn move_cursor_up( - stderr_lock: &mut StderrLock, + stderr_lock: &mut std::io::StderrLock, ) -> Result<(), std::io::Error> { write!(stderr_lock, "\x1B[1A") } @@ -270,7 +283,9 @@ fn clear_stdin( } // Clear n-lines in terminal and move cursor to the beginning of the line. -fn clear_n_lines(stderr_lock: &mut StderrLock, n: usize) { +#[cfg(not(target_arch = "wasm32"))] +fn clear_n_lines(stderr_lock: &mut std::io::StderrLock, n: usize) { + use std::io::Write; write!(stderr_lock, "\x1B[{n}A\x1B[0J").unwrap(); } @@ -289,6 +304,11 @@ fn get_stdin_metadata() -> std::io::Result { } } +#[cfg(not(target_arch = "wasm32"))] +#[derive(Default)] +pub struct TtyPrompter; + +#[cfg(not(target_arch = "wasm32"))] impl PermissionPrompter for TtyPrompter { fn prompt( &mut self, @@ -298,6 +318,16 @@ impl PermissionPrompter for TtyPrompter { is_unary: bool, get_stack: Option, ) -> PromptResponse { + use std::fmt::Write; + use std::io::BufRead; + use std::io::IsTerminal; + use std::io::Write as IoWrite; + + use deno_terminal::colors; + + // 10kB of permission prompting should be enough for anyone + const MAX_PERMISSION_PROMPT_LENGTH: usize = 10 * 1024; + if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() { return PromptResponse::Deny; }; @@ -381,7 +411,7 @@ impl PermissionPrompter for TtyPrompter { )) ); writeln!(&mut output, "┠─ {}", colors::italic(&msg)).unwrap(); - let msg = if is_standalone() { + let msg = if crate::is_standalone() { format!("Specify the required permissions during compile time using `deno compile --allow-{name}`.") } else { format!("Run again with --allow-{name} to bypass this prompt.") diff --git a/runtime/permissions/which.rs b/runtime/permissions/which.rs new file mode 100644 index 0000000000..be1960b62c --- /dev/null +++ b/runtime/permissions/which.rs @@ -0,0 +1,186 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +use std::ffi::OsStr; +use std::ffi::OsString; +use std::path::PathBuf; + +use temp_deno_which::sys::Sys; +pub use temp_deno_which::Error; + +pub fn which_in( + sys: impl WhichSys, + binary_name: &str, + path: Option, + cwd: PathBuf, +) -> Result { + let sys = WhichSysAdapter(sys); + let config = temp_deno_which::WhichConfig::new_with_sys(sys) + .custom_cwd(cwd) + .binary_name(OsString::from(binary_name)); + let config = match path { + Some(path) => config.custom_path_list(path), + None => config, + }; + config.first_result() +} + +#[sys_traits::auto_impl] +pub trait WhichSys: + sys_traits::EnvHomeDir + + sys_traits::EnvCurrentDir + + sys_traits::EnvVar + + sys_traits::FsReadDir + + sys_traits::FsMetadata + + Clone + + 'static +{ +} + +#[derive(Clone)] +pub struct WhichSysAdapter(TSys); + +impl Sys for WhichSysAdapter { + type ReadDirEntry = WhichReadDirEntrySysAdapter; + + type Metadata = WhichMetadataSysAdapter; + + fn is_windows(&self) -> bool { + sys_traits::impls::is_windows() + } + + fn current_dir(&self) -> std::io::Result { + self.0.env_current_dir() + } + + fn home_dir(&self) -> Option { + self.0.env_home_dir() + } + + fn env_split_paths(&self, paths: &OsStr) -> Vec { + if cfg!(target_arch = "wasm32") && self.is_windows() { + // not perfect, but good enough + paths + .to_string_lossy() + .split(";") + .map(PathBuf::from) + .collect() + } else { + std::env::split_paths(paths).collect() + } + } + + fn env_var_os(&self, name: &OsStr) -> Option { + self.0.env_var_os(name) + } + + fn metadata( + &self, + path: &std::path::Path, + ) -> std::io::Result { + self.0.fs_metadata(path).map(WhichMetadataSysAdapter) + } + + fn symlink_metadata( + &self, + path: &std::path::Path, + ) -> std::io::Result { + self + .0 + .fs_symlink_metadata(path) + .map(WhichMetadataSysAdapter) + } + + fn read_dir( + &self, + path: &std::path::Path, + ) -> std::io::Result< + Box>>, + > { + let iter = self.0.fs_read_dir(path)?; + let iter = Box::new( + iter + .into_iter() + .map(|value| value.map(WhichReadDirEntrySysAdapter)), + ); + Ok(iter) + } + + #[cfg(unix)] + fn is_valid_executable( + &self, + path: &std::path::Path, + ) -> std::io::Result { + use nix::unistd::access; + use nix::unistd::AccessFlags; + + match access(path, AccessFlags::X_OK) { + Ok(()) => Ok(true), + Err(nix::errno::Errno::ENOENT) => Ok(false), + Err(e) => Err(std::io::Error::from_raw_os_error(e as i32)), + } + } + + #[cfg(target_arch = "wasm32")] + fn is_valid_executable( + &self, + _path: &std::path::Path, + ) -> std::io::Result { + Ok(true) + } + + #[cfg(windows)] + fn is_valid_executable( + &self, + path: &std::path::Path, + ) -> std::io::Result { + use std::os::windows::ffi::OsStrExt; + + let name = path + .as_os_str() + .encode_wide() + .chain(Some(0)) + .collect::>(); + let mut bt: u32 = 0; + // SAFETY: winapi call + unsafe { + Ok( + windows_sys::Win32::Storage::FileSystem::GetBinaryTypeW( + name.as_ptr(), + &mut bt, + ) != 0, + ) + } + } +} + +pub struct WhichReadDirEntrySysAdapter( + TFsDirEntry, +); + +impl temp_deno_which::sys::SysReadDirEntry + for WhichReadDirEntrySysAdapter +{ + fn file_name(&self) -> std::ffi::OsString { + self.0.file_name().into_owned() + } + + fn path(&self) -> std::path::PathBuf { + self.0.path().into_owned() + } +} + +pub struct WhichMetadataSysAdapter( + TMetadata, +); + +impl temp_deno_which::sys::SysMetadata + for WhichMetadataSysAdapter +{ + fn is_symlink(&self) -> bool { + self.0.file_type().is_symlink() + } + + fn is_file(&self) -> bool { + self.0.file_type().is_file() + } +}