diff --git a/Cargo.lock b/Cargo.lock index 3039770559..ce71919953 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1974,9 +1974,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.134" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] name = "libgit2-sys" @@ -2583,6 +2583,7 @@ dependencies = [ "is-root", "itertools", "lazy_static", + "libc", "log", "lscolors", "md-5", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 4ca7377315..ae5154d70f 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -98,6 +98,7 @@ winreg = "0.10.1" [target.'cfg(unix)'.dependencies] umask = "2.0.0" users = "0.11.0" +libc = "0.2" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash] version = "2.1.3" diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 1812efb632..c7ba6cbe74 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -6,6 +6,29 @@ use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, }; +use std::path::Path; + +// when the file under the fold executeable +#[cfg(unix)] +mod permission_mods { + pub type Mode = u32; + pub mod unix { + use super::Mode; + pub const USER_EXECUTE: Mode = libc::S_IXUSR as Mode; + pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode; + pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode; + } +} + +// use to return the message of the result of change director +// TODO: windows, maybe should use file_attributes function in https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html +// TODO: the meaning of the result of the function can be found in https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants +// TODO: if have realize the logic on windows, remove the cfg +#[derive(Debug)] +enum PermissionResult<'a> { + PermissionOk, + PermissionDenied(&'a str), +} #[derive(Clone)] pub struct Cd; @@ -141,6 +164,7 @@ impl Command for Cd { } }; + let path_tointo = path.clone(); let path_value = Value::String { val: path, span }; let cwd = Value::String { val: cwd.to_string_lossy().to_string(), @@ -172,9 +196,16 @@ impl Command for Cd { //FIXME: this only changes the current scope, but instead this environment variable //should probably be a block that loads the information from the state in the overlay - - stack.add_env_var("PWD".into(), path_value); - Ok(PipelineData::new(call.head)) + match have_permission(&path_tointo) { + PermissionResult::PermissionOk => { + stack.add_env_var("PWD".into(), path_value); + Ok(PipelineData::new(call.head)) + } + PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError(format!( + "Cannot change directory to {}: {}", + path_tointo, reason + ))), + } } fn examples(&self) -> Vec { @@ -197,3 +228,65 @@ impl Command for Cd { ] } } +#[cfg(windows)] +fn have_permission(dir: impl AsRef) -> PermissionResult<'static> { + match dir.as_ref().read_dir() { + Err(e) => { + if matches!(e.kind(), std::io::ErrorKind::PermissionDenied) { + PermissionResult::PermissionDenied("Folder is unable to be read") + } else { + PermissionResult::PermissionOk + } + } + Ok(_) => PermissionResult::PermissionOk, + } +} + +#[cfg(unix)] +fn have_permission(dir: impl AsRef) -> PermissionResult<'static> { + match dir.as_ref().metadata() { + Ok(metadata) => { + use std::os::unix::fs::MetadataExt; + let bits = metadata.mode(); + let has_bit = |bit| bits & bit == bit; + let current_user = users::get_current_uid(); + if current_user == 0 { + return PermissionResult::PermissionOk; + } + let current_group = users::get_current_gid(); + let owner_user = metadata.uid(); + let owner_group = metadata.gid(); + match (current_user == owner_user, current_group == owner_group) { + (true, _) => { + if has_bit(permission_mods::unix::USER_EXECUTE) { + PermissionResult::PermissionOk + } else { + PermissionResult::PermissionDenied( + "You are the owner but do not have the execute permission", + ) + } + } + (false, true) => { + if has_bit(permission_mods::unix::GROUP_EXECUTE) { + PermissionResult::PermissionOk + } else { + PermissionResult::PermissionDenied( + "You are in the group but do not have the execute permission", + ) + } + } + // other_user or root + (false, false) => { + if has_bit(permission_mods::unix::OTHER_EXECUTE) { + PermissionResult::PermissionOk + } else { + PermissionResult::PermissionDenied( + "You are neither the owner, in the group, nor the super user and do not have permission", + ) + } + } + } + } + Err(_) => PermissionResult::PermissionDenied("Could not retrieve the metadata"), + } +} diff --git a/crates/nu-command/tests/commands/cd.rs b/crates/nu-command/tests/commands/cd.rs index 89ddbfed3e..9165fda996 100644 --- a/crates/nu-command/tests/commands/cd.rs +++ b/crates/nu-command/tests/commands/cd.rs @@ -289,3 +289,43 @@ fn test_change_windows_drive() { .exists()); }) } + +#[cfg(unix)] +#[test] +fn cd_permission_deined_folder() { + Playground::setup("cd_test_21", |dirs, sandbox| { + sandbox.mkdir("banned"); + let actual = nu!( + cwd: dirs.test(), + r#" + chmod -x banned + cd banned + "# + ); + assert!(actual.err.contains("Cannot change directory to")); + nu!( + cwd: dirs.test(), + r#" + chmod +x banned + rm banned + "# + ); + }); +} +// FIXME: cd_permission_deined_folder on windows +#[ignore] +#[cfg(windows)] +#[test] +fn cd_permission_deined_folder() { + Playground::setup("cd_test_21", |dirs, sandbox| { + sandbox.mkdir("banned"); + let actual = nu!( + cwd: dirs.test(), + r#" + icacls banned /deny BUILTIN\Administrators:F + cd banned + "# + ); + assert!(actual.err.contains("Folder is not able to read")); + }); +}