From 990d127d04cd11025bc49338b9e2e7e2b12b20af Mon Sep 17 00:00:00 2001 From: David Golembiowski Date: Thu, 27 May 2021 00:58:24 -0400 Subject: [PATCH 1/6] Currently exploring what it takes to make this happen on Linux and Unix OS's. Maybe the API usage looks like: ```rs fn main() { let path = Path::new("..."); if path.is_permitted()? .is_executable() { // ... } profit!(); } ``` and ideally initiate a division of concerns between Linux-y/Unix-y permission bit flags, as well as whatever Windows has in store. --- Cargo.toml | 3 ++ src/lib.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c4db7b..b350503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,6 @@ winapi = { version = "0.3", features = ["winbase"] } [dev-dependencies] diff = "0.1.10" + +[dependencies] +users = "0.11.0" diff --git a/src/lib.rs b/src/lib.rs index e08c3e0..580e2d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,9 @@ The API comes in two flavors: #[cfg(target_os = "windows")] extern crate winapi; +use std::io; +use std::env; +use std::os; use std::path::Path; /// Returns `true` if there is a file at the given path and it is @@ -68,6 +71,8 @@ where path.as_ref().is_executable() } + + /// An extension trait for `std::fs::Path` providing an `is_executable` method. /// /// See the module documentation for examples. @@ -79,12 +84,44 @@ pub trait IsExecutable { fn is_executable(&self) -> bool; } +/// Returns `Result` if there is a file at the given path and the +/// current run-level is permitted to execute it. +/// +/// See the module documentation for details. +pub fn is_permitted

(path: P) -> io::Result +where + P: AsRef +{ + path.as_ref().is_permitted() +} + +/// An extension trait for `std::fs::Path` providing an `is_permitted` method. +/// +/// See the module documentation for examples. +pub trait IsPermitted { + /// Returns `Result` that describes if a particular file + /// exists at the given path and the run-level of the current context meets + /// the appropriate user, group, admin, root/system-level membership. + /// + /// Note: *this does not inspect whether the `Path` is executable.* + fn is_permitted(&self) -> io::Result; +} + #[cfg(unix)] mod unix { - use std::os::unix::fs::PermissionsExt; + use std::os::unix::fs::MetadataExt; + use users::{ + Group, Groups, + User, Users, + group_access_list, + get_effective_uid, + get_group_by_gid + }; use std::path::Path; - + use std::fs; + use std::io; use super::IsExecutable; + use super::IsPermitted; impl IsExecutable for Path { fn is_executable(&self) -> bool { @@ -96,6 +133,47 @@ mod unix { metadata.is_file() && permissions.mode() & 0o111 != 0 } } + + macro_rules! vecomp { + [$expr:expr; for $pat:pat in $iter:expr $(; if $cond:expr )?] => { + IntoIterator::into_iter($iter) + $( + .filter(|$pat| $cond) + )? + .map(|$pat| $expr) + .collect::>() + } + } + + impl IsPermitted for Path { + fn is_permitted(&self) -> io::Result { + if let Ok(metadata) = self.borrow().metadata() { + /** If the metadata's mode's octal bits match any + * one of the combinations listed below, then we + * have sufficient reasons to believe it's executable + */ + match metadata.borrow().is_file() { + true => { + /** Could this target path be ran with executable permissions + * by this runtime's run-level user? + * + * Let's find out. + */ + + }, + false => { + io::Error::new(io::ErrorKind::NotFound, + format!("{:?} was not readable or present on the local filesystem", + self.to_string())) + } + } + } else { + io::Error::last_os_error() + } + } + } + + impl From> for AsRef } #[cfg(target_os = "windows")] From 15810899f75e47b8151740a83d7b5d7f7ba8b603 Mon Sep 17 00:00:00 2001 From: David Golembiowski Date: Fri, 28 May 2021 00:57:54 -0400 Subject: [PATCH 2/6] Many errors. I've tried to provide a simple but airtight line of logic in the documentation. At that's left to do is clean up the type signatures, fat pointers, and borrows. --- src/lib.rs | 57 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 580e2d5..78f3ccb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ pub trait IsExecutable { /// current run-level is permitted to execute it. /// /// See the module documentation for details. -pub fn is_permitted

(path: P) -> io::Result +pub fn is_permitted

(path: P) -> io::Result> where P: AsRef { @@ -104,18 +104,17 @@ pub trait IsPermitted { /// the appropriate user, group, admin, root/system-level membership. /// /// Note: *this does not inspect whether the `Path` is executable.* - fn is_permitted(&self) -> io::Result; + fn is_permitted(&self) -> io::Result>; } #[cfg(unix)] mod unix { use std::os::unix::fs::MetadataExt; + extern crate users; use users::{ - Group, Groups, - User, Users, group_access_list, get_effective_uid, - get_group_by_gid + //get_effective_grouplist, }; use std::path::Path; use std::fs; @@ -146,20 +145,42 @@ mod unix { } impl IsPermitted for Path { - fn is_permitted(&self) -> io::Result { + fn is_permitted(&self) -> io::Result> { if let Ok(metadata) = self.borrow().metadata() { - /** If the metadata's mode's octal bits match any - * one of the combinations listed below, then we - * have sufficient reasons to believe it's executable - */ + /// If the metadata's mode's octal bits match any + /// one of the combinations listed below, then we + /// have sufficient reasons to believe it's executable. match metadata.borrow().is_file() { true => { - /** Could this target path be ran with executable permissions - * by this runtime's run-level user? - * - * Let's find out. - */ - + /// Could this target path be ran with executable permissions + /// by this runtime's run-level user? + /// + /// Suppose the GID for the file in question is (Gm). + /// Assuming that the set of all groups shared by the user, + /// (G* := { Gn, Gn+1, Gn+2, ...}), is a superset of the set whose only + /// entry is the user's GID, (G_user := G* - Gn). + /// Then, Ǝ a possibility some arbitary GID, like (Gn+14) for example, + /// happens to be the GID belonging to group on that file. + /// + /// In other words, we'll collect each of the (G*) entries + /// and see if there's a match. Otherwise, we check who owns the file and + /// perform a similar check. + if let Some(_arbitrary_gid_matched) = + vecomp![ + fs::metadata(self.borrow().to_str().unwrap()).gid()? == Ok(gid); + for gid in group_access_list() + ].into_iter().take_while(|b| b == false).last() { + Ok(Box::new(Self)) + } else if fs::metadata(self.borrow().to_str().unwrap()).uid()? == Ok(get_effective_uid() as u32) { + /// (It's *really* unusual for this case to occur) + /// If a terminal operator or script executed + /// `chmod {1,3,5,7}{2,4,6}{2,4,6} $FILE` (i.e. chmod 720 ~/.bashrc), + /// the corner case of having owner-exclusive execution rights + /// without identical group executable permissions is a thing that can happen. + Ok(Box::new(Self)) + } else { + io::Error::new(io::ErrorKind::PermissionDenied, "Access Denied") + } }, false => { io::Error::new(io::ErrorKind::NotFound, @@ -168,12 +189,10 @@ mod unix { } } } else { - io::Error::last_os_error() + io::Error::new(io::Error::last_os_error() } } } - - impl From> for AsRef } #[cfg(target_os = "windows")] From 5073676a9189584129309ff1991518033cb3801a Mon Sep 17 00:00:00 2001 From: David Golembiowski Date: Fri, 4 Jun 2021 22:16:53 -0400 Subject: [PATCH 3/6] I find this works better: returning an Option of a PathBuf, because it implements Deref. --- src/lib.rs | 54 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 78f3ccb..5daeeee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,8 +58,8 @@ extern crate winapi; use std::io; use std::env; use std::os; -use std::path::Path; - +use std::path::{Path, PathBuf}; +use std::os::unix::fs::PermissionsExt; /// Returns `true` if there is a file at the given path and it is /// executable. Returns `false` otherwise. /// @@ -88,7 +88,7 @@ pub trait IsExecutable { /// current run-level is permitted to execute it. /// /// See the module documentation for details. -pub fn is_permitted

(path: P) -> io::Result> +pub fn is_permitted

(path: P) -> Option<::std::path::PathBuf> //io::Result> where P: AsRef { @@ -104,18 +104,21 @@ pub trait IsPermitted { /// the appropriate user, group, admin, root/system-level membership. /// /// Note: *this does not inspect whether the `Path` is executable.* - fn is_permitted(&self) -> io::Result>; + fn is_permitted(&self) -> Option<::std::path::PathBuf>; } #[cfg(unix)] mod unix { use std::os::unix::fs::MetadataExt; + use std::os::unix::fs::PermissionsExt; + extern crate users; - use users::{ + use self::users::{ group_access_list, get_effective_uid, //get_effective_grouplist, }; + use std::cell::RefCell; use std::path::Path; use std::fs; use std::io; @@ -132,7 +135,7 @@ mod unix { metadata.is_file() && permissions.mode() & 0o111 != 0 } } - + /* macro_rules! vecomp { [$expr:expr; for $pat:pat in $iter:expr $(; if $cond:expr )?] => { IntoIterator::into_iter($iter) @@ -143,10 +146,36 @@ mod unix { .collect::>() } } - + */ impl IsPermitted for Path { - fn is_permitted(&self) -> io::Result> { - if let Ok(metadata) = self.borrow().metadata() { + fn is_permitted(&self /* : &Path */) -> Option<::std::path::PathBuf> { + // Do an interiorly-mutable borrow on `self` + // to give us access to `PathBuf` + + // let mut refc_path: RefCell = RefCell::new(*self); + let (metadata, buf) = match self.metadata() { + Ok(md) => { (Some(md), self.to_path_buf()) }, + Err(_) => { (None, self.to_path_buf()) } + }; + match metadata.unwrap().is_file() { + true => { + let file_gid: u32 = fs::metadata(buf.to_str().unwrap()).unwrap().gid(); + if let Some(_gid_match) = group_access_list() /* := Result> */ + .unwrap() + .into_iter() + .take_while(|grp| file_gid != grp.gid()) + .last() { + return Some(buf) + } + else { + todo!() + } + } + false => { None } + } + } + /* + if let Ok(metadata) = path_buf.borrow_mut().metadata() { /// If the metadata's mode's octal bits match any /// one of the combinations listed below, then we /// have sufficient reasons to believe it's executable. @@ -167,7 +196,10 @@ mod unix { /// perform a similar check. if let Some(_arbitrary_gid_matched) = vecomp![ - fs::metadata(self.borrow().to_str().unwrap()).gid()? == Ok(gid); + fs::metadata( + self.borrow() + .to_str() + .unwrap()).gid() == Ok(gid); for gid in group_access_list() ].into_iter().take_while(|b| b == false).last() { Ok(Box::new(Self)) @@ -191,7 +223,7 @@ mod unix { } else { io::Error::new(io::Error::last_os_error() } - } + */ } } From 6bf37b052f6965c79e5801dabc15e8d701caa336 Mon Sep 17 00:00:00 2001 From: David Golembiowski Date: Fri, 4 Jun 2021 23:30:09 -0400 Subject: [PATCH 4/6] binary operation cannot be applied to type --- ] | 128 +++++++++++++++++++++++ src/lib.rs | 121 ++++++--------------- tests/i_am_permitted_by_file_ownership | 1 + tests/i_am_permitted_by_group_membership | 1 + tests/tests.rs | 30 +++++- 5 files changed, 192 insertions(+), 89 deletions(-) create mode 100644 ] create mode 100755 tests/i_am_permitted_by_file_ownership create mode 100644 tests/i_am_permitted_by_group_membership diff --git a/] b/] new file mode 100644 index 0000000..ee49337 --- /dev/null +++ b/] @@ -0,0 +1,128 @@ +extern crate diff; +extern crate is_executable; + +use is_executable::{ is_executable, is_permitted }; + +#[cfg(unix)] +mod unix { + use std::env; + use std::fs::File; + use std::io::Read; + use std::process::Command; + + use super::*; + + #[test] + fn cargo_readme_up_to_date() { + if env::var("CI").is_ok() { + return; + } + + println!("Checking that `cargo readme > README.md` is up to date..."); + + let expected = Command::new("cargo") + .arg("readme") + .current_dir(env!("CARGO_MANIFEST_DIR")) + .output() + .expect("should run `cargo readme` OK") + .stdout; + let expected = String::from_utf8_lossy(&expected); + + let actual = { + let mut file = File::open(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md")) + .expect("should open README.md file"); + let mut s = String::new(); + file.read_to_string(&mut s) + .expect("should read contents of file to string"); + s + }; + + if actual != expected { + println!(); + println!("+++ expected README.md"); + println!("--- actual README.md"); + for d in diff::lines(&expected, &actual) { + match d { + diff::Result::Left(l) => println!("+{}", l), + diff::Result::Right(r) => println!("-{}", r), + diff::Result::Both(b, _) => println!(" {}", b), + } + } + panic!("Run `cargo readme > README.md` to update README.md") + } + } + + #[test] + fn executable() { + assert!(is_executable("./tests/i_am_executable")); + } + + #[test] + fn executable_symlink() { + assert!(is_executable("./tests/i_am_executable_and_symlink")); + } + + #[test] + fn not_executable_symlink() { + assert!(!is_executable("./tests/i_am_not_executable_and_symlink")); + } + + #[test] + fn not_executable_directory() { + assert!(!is_executable(".")); + } + + #[test] + fn permitted_by_membership() { + // `chmod 670 ./tests/i_am_permitted_by_group_membership` + let path: &str = "./tests/i_am_permitted_by_group_membership"; + { + assert_eq!( + is_permitted(path), + Ok(Some(::std::path::PathBuf::from(path))), + "Testing whether the file's GID is in the set of the user's groups" + ); + } + } + + #[test] + fn permitted_by_ownership() { + // `chmod 700 ./tests/i_am_permitted_by_file_ownership` + let path: &str = "./tests/i_am_permitted_by_file_ownership"; + { + assert_eq!( + is_permitted(path), + Ok(Some(::std::path::PathBuf::from(path))), + "Testing whether the file's UID bits exclusively allow owners to execute + this file, and that the UID of this user is happens to be the file's owner." + ); + } + } + +} + +#[cfg(target_os = "windows")] +mod windows { + use super::*; + + #[test] + fn executable() { + assert!(is_executable("C:\\Windows\\explorer.exe")); + } + + #[test] + fn by_extension() { + assert!(is_executable("./tests/i_am_executable_on_windows.bat")); + } + +} + +#[test] +fn not_executable() { + assert!(!is_executable("./tests/i_am_not_executable")); +} + +#[test] +fn non_existant() { + assert!(!is_executable("./tests/this-file-does-not-exist")); +} diff --git a/src/lib.rs b/src/lib.rs index 5daeeee..39f78fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,9 +56,7 @@ The API comes in two flavors: extern crate winapi; use std::io; -use std::env; -use std::os; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::os::unix::fs::PermissionsExt; /// Returns `true` if there is a file at the given path and it is /// executable. Returns `false` otherwise. @@ -88,7 +86,7 @@ pub trait IsExecutable { /// current run-level is permitted to execute it. /// /// See the module documentation for details. -pub fn is_permitted

(path: P) -> Option<::std::path::PathBuf> //io::Result> +pub fn is_permitted

(path: P) -> Result, io::Error> where P: AsRef { @@ -104,11 +102,12 @@ pub trait IsPermitted { /// the appropriate user, group, admin, root/system-level membership. /// /// Note: *this does not inspect whether the `Path` is executable.* - fn is_permitted(&self) -> Option<::std::path::PathBuf>; + fn is_permitted(&self) -> Result, ::std::io::Error>; } #[cfg(unix)] mod unix { + use Path; use std::os::unix::fs::MetadataExt; use std::os::unix::fs::PermissionsExt; @@ -116,12 +115,8 @@ mod unix { use self::users::{ group_access_list, get_effective_uid, - //get_effective_grouplist, - }; - use std::cell::RefCell; - use std::path::Path; - use std::fs; - use std::io; + }; + use std::fs; use super::IsExecutable; use super::IsPermitted; @@ -135,95 +130,45 @@ mod unix { metadata.is_file() && permissions.mode() & 0o111 != 0 } } - /* - macro_rules! vecomp { - [$expr:expr; for $pat:pat in $iter:expr $(; if $cond:expr )?] => { - IntoIterator::into_iter($iter) - $( - .filter(|$pat| $cond) - )? - .map(|$pat| $expr) - .collect::>() - } - } - */ - impl IsPermitted for Path { - fn is_permitted(&self /* : &Path */) -> Option<::std::path::PathBuf> { - // Do an interiorly-mutable borrow on `self` - // to give us access to `PathBuf` - // let mut refc_path: RefCell = RefCell::new(*self); + /// Could this target path be ran with executable permissions + /// by this runtime's run-level user? + /// + /// Suppose the GID for the file in question is (Gm). + /// Assuming that the set of all groups shared by the user, + /// (G* := { Gn, Gn+1, Gn+2, ...}), is a superset of the set whose only + /// entry is the user's GID, (G_user := G* - Gn). + /// Then, Ǝ a possibility some arbitary GID, like (Gn+14) for example, + /// happens to be the GID belonging to group on that file. + /// + /// In other words, we'll collect each of the (G*) entries + /// and see if there's a match. Otherwise, we check who owns the file and + /// perform a similar check. + impl IsPermitted for Path { + fn is_permitted(&self) -> Result, ::std::io::Error> { let (metadata, buf) = match self.metadata() { Ok(md) => { (Some(md), self.to_path_buf()) }, - Err(_) => { (None, self.to_path_buf()) } + Err(e) => { return Err(e) } }; match metadata.unwrap().is_file() { true => { let file_gid: u32 = fs::metadata(buf.to_str().unwrap()).unwrap().gid(); - if let Some(_gid_match) = group_access_list() /* := Result> */ - .unwrap() - .into_iter() - .take_while(|grp| file_gid != grp.gid()) - .last() { - return Some(buf) + if let Some(_gid_match) = group_access_list() + .unwrap() + .into_iter() + .take_while(|grp| file_gid != grp.gid()) + .last() { return Ok(Some(buf)) } + else if fs::metadata(self.to_str().unwrap()) + .unwrap().uid() == get_effective_uid() { + Ok(Some(buf)) } else { - todo!() + Err(::std::io::Error::new(::std::io::ErrorKind::PermissionDenied, "Access denied.")) } } - false => { None } + false => { Err(::std::io::Error::new(::std::io::ErrorKind::NotFound, "Path not found")) }, } - } - /* - if let Ok(metadata) = path_buf.borrow_mut().metadata() { - /// If the metadata's mode's octal bits match any - /// one of the combinations listed below, then we - /// have sufficient reasons to believe it's executable. - match metadata.borrow().is_file() { - true => { - /// Could this target path be ran with executable permissions - /// by this runtime's run-level user? - /// - /// Suppose the GID for the file in question is (Gm). - /// Assuming that the set of all groups shared by the user, - /// (G* := { Gn, Gn+1, Gn+2, ...}), is a superset of the set whose only - /// entry is the user's GID, (G_user := G* - Gn). - /// Then, Ǝ a possibility some arbitary GID, like (Gn+14) for example, - /// happens to be the GID belonging to group on that file. - /// - /// In other words, we'll collect each of the (G*) entries - /// and see if there's a match. Otherwise, we check who owns the file and - /// perform a similar check. - if let Some(_arbitrary_gid_matched) = - vecomp![ - fs::metadata( - self.borrow() - .to_str() - .unwrap()).gid() == Ok(gid); - for gid in group_access_list() - ].into_iter().take_while(|b| b == false).last() { - Ok(Box::new(Self)) - } else if fs::metadata(self.borrow().to_str().unwrap()).uid()? == Ok(get_effective_uid() as u32) { - /// (It's *really* unusual for this case to occur) - /// If a terminal operator or script executed - /// `chmod {1,3,5,7}{2,4,6}{2,4,6} $FILE` (i.e. chmod 720 ~/.bashrc), - /// the corner case of having owner-exclusive execution rights - /// without identical group executable permissions is a thing that can happen. - Ok(Box::new(Self)) - } else { - io::Error::new(io::ErrorKind::PermissionDenied, "Access Denied") - } - }, - false => { - io::Error::new(io::ErrorKind::NotFound, - format!("{:?} was not readable or present on the local filesystem", - self.to_string())) - } - } - } else { - io::Error::new(io::Error::last_os_error() - } - */ + } } } diff --git a/tests/i_am_permitted_by_file_ownership b/tests/i_am_permitted_by_file_ownership new file mode 100755 index 0000000..8b13789 --- /dev/null +++ b/tests/i_am_permitted_by_file_ownership @@ -0,0 +1 @@ + diff --git a/tests/i_am_permitted_by_group_membership b/tests/i_am_permitted_by_group_membership new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/i_am_permitted_by_group_membership @@ -0,0 +1 @@ + diff --git a/tests/tests.rs b/tests/tests.rs index d4a9458..ee49337 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,7 +1,7 @@ extern crate diff; extern crate is_executable; -use is_executable::is_executable; +use is_executable::{ is_executable, is_permitted }; #[cfg(unix)] mod unix { @@ -71,6 +71,34 @@ mod unix { fn not_executable_directory() { assert!(!is_executable(".")); } + + #[test] + fn permitted_by_membership() { + // `chmod 670 ./tests/i_am_permitted_by_group_membership` + let path: &str = "./tests/i_am_permitted_by_group_membership"; + { + assert_eq!( + is_permitted(path), + Ok(Some(::std::path::PathBuf::from(path))), + "Testing whether the file's GID is in the set of the user's groups" + ); + } + } + + #[test] + fn permitted_by_ownership() { + // `chmod 700 ./tests/i_am_permitted_by_file_ownership` + let path: &str = "./tests/i_am_permitted_by_file_ownership"; + { + assert_eq!( + is_permitted(path), + Ok(Some(::std::path::PathBuf::from(path))), + "Testing whether the file's UID bits exclusively allow owners to execute + this file, and that the UID of this user is happens to be the file's owner." + ); + } + } + } #[cfg(target_os = "windows")] From e8755dc34fcc87e4eed3f59d1eb9acbe91ec0bf7 Mon Sep 17 00:00:00 2001 From: David Golembiowski Date: Fri, 4 Jun 2021 23:47:00 -0400 Subject: [PATCH 5/6] All tests passed! --- ] | 128 ------------------------------------------------- src/lib.rs | 10 ++-- tests/tests.rs | 10 ++-- 3 files changed, 11 insertions(+), 137 deletions(-) delete mode 100644 ] diff --git a/] b/] deleted file mode 100644 index ee49337..0000000 --- a/] +++ /dev/null @@ -1,128 +0,0 @@ -extern crate diff; -extern crate is_executable; - -use is_executable::{ is_executable, is_permitted }; - -#[cfg(unix)] -mod unix { - use std::env; - use std::fs::File; - use std::io::Read; - use std::process::Command; - - use super::*; - - #[test] - fn cargo_readme_up_to_date() { - if env::var("CI").is_ok() { - return; - } - - println!("Checking that `cargo readme > README.md` is up to date..."); - - let expected = Command::new("cargo") - .arg("readme") - .current_dir(env!("CARGO_MANIFEST_DIR")) - .output() - .expect("should run `cargo readme` OK") - .stdout; - let expected = String::from_utf8_lossy(&expected); - - let actual = { - let mut file = File::open(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md")) - .expect("should open README.md file"); - let mut s = String::new(); - file.read_to_string(&mut s) - .expect("should read contents of file to string"); - s - }; - - if actual != expected { - println!(); - println!("+++ expected README.md"); - println!("--- actual README.md"); - for d in diff::lines(&expected, &actual) { - match d { - diff::Result::Left(l) => println!("+{}", l), - diff::Result::Right(r) => println!("-{}", r), - diff::Result::Both(b, _) => println!(" {}", b), - } - } - panic!("Run `cargo readme > README.md` to update README.md") - } - } - - #[test] - fn executable() { - assert!(is_executable("./tests/i_am_executable")); - } - - #[test] - fn executable_symlink() { - assert!(is_executable("./tests/i_am_executable_and_symlink")); - } - - #[test] - fn not_executable_symlink() { - assert!(!is_executable("./tests/i_am_not_executable_and_symlink")); - } - - #[test] - fn not_executable_directory() { - assert!(!is_executable(".")); - } - - #[test] - fn permitted_by_membership() { - // `chmod 670 ./tests/i_am_permitted_by_group_membership` - let path: &str = "./tests/i_am_permitted_by_group_membership"; - { - assert_eq!( - is_permitted(path), - Ok(Some(::std::path::PathBuf::from(path))), - "Testing whether the file's GID is in the set of the user's groups" - ); - } - } - - #[test] - fn permitted_by_ownership() { - // `chmod 700 ./tests/i_am_permitted_by_file_ownership` - let path: &str = "./tests/i_am_permitted_by_file_ownership"; - { - assert_eq!( - is_permitted(path), - Ok(Some(::std::path::PathBuf::from(path))), - "Testing whether the file's UID bits exclusively allow owners to execute - this file, and that the UID of this user is happens to be the file's owner." - ); - } - } - -} - -#[cfg(target_os = "windows")] -mod windows { - use super::*; - - #[test] - fn executable() { - assert!(is_executable("C:\\Windows\\explorer.exe")); - } - - #[test] - fn by_extension() { - assert!(is_executable("./tests/i_am_executable_on_windows.bat")); - } - -} - -#[test] -fn not_executable() { - assert!(!is_executable("./tests/i_am_not_executable")); -} - -#[test] -fn non_existant() { - assert!(!is_executable("./tests/this-file-does-not-exist")); -} diff --git a/src/lib.rs b/src/lib.rs index 39f78fd..c292e5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,7 +86,7 @@ pub trait IsExecutable { /// current run-level is permitted to execute it. /// /// See the module documentation for details. -pub fn is_permitted

(path: P) -> Result, io::Error> +pub fn is_permitted

(path: P) -> Result<::std::path::PathBuf, io::Error> where P: AsRef { @@ -102,7 +102,7 @@ pub trait IsPermitted { /// the appropriate user, group, admin, root/system-level membership. /// /// Note: *this does not inspect whether the `Path` is executable.* - fn is_permitted(&self) -> Result, ::std::io::Error>; + fn is_permitted(&self) -> Result<::std::path::PathBuf, ::std::io::Error>; } #[cfg(unix)] @@ -145,7 +145,7 @@ mod unix { /// and see if there's a match. Otherwise, we check who owns the file and /// perform a similar check. impl IsPermitted for Path { - fn is_permitted(&self) -> Result, ::std::io::Error> { + fn is_permitted(&self) -> Result<::std::path::PathBuf, ::std::io::Error> { let (metadata, buf) = match self.metadata() { Ok(md) => { (Some(md), self.to_path_buf()) }, Err(e) => { return Err(e) } @@ -157,10 +157,10 @@ mod unix { .unwrap() .into_iter() .take_while(|grp| file_gid != grp.gid()) - .last() { return Ok(Some(buf)) } + .last() { return Ok(buf) } else if fs::metadata(self.to_str().unwrap()) .unwrap().uid() == get_effective_uid() { - Ok(Some(buf)) + Ok(buf) } else { Err(::std::io::Error::new(::std::io::ErrorKind::PermissionDenied, "Access denied.")) diff --git a/tests/tests.rs b/tests/tests.rs index ee49337..929db94 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -77,9 +77,10 @@ mod unix { // `chmod 670 ./tests/i_am_permitted_by_group_membership` let path: &str = "./tests/i_am_permitted_by_group_membership"; { + let check = is_permitted(path).ok().unwrap(); assert_eq!( - is_permitted(path), - Ok(Some(::std::path::PathBuf::from(path))), + check, + ::std::path::PathBuf::from(path), "Testing whether the file's GID is in the set of the user's groups" ); } @@ -90,9 +91,10 @@ mod unix { // `chmod 700 ./tests/i_am_permitted_by_file_ownership` let path: &str = "./tests/i_am_permitted_by_file_ownership"; { + let check = is_permitted(path).ok().unwrap(); assert_eq!( - is_permitted(path), - Ok(Some(::std::path::PathBuf::from(path))), + check, + ::std::path::PathBuf::from(path), "Testing whether the file's UID bits exclusively allow owners to execute this file, and that the UID of this user is happens to be the file's owner." ); From 4cb0ec7f40bc1b40ddb6deb211690904bc4015ce Mon Sep 17 00:00:00 2001 From: David Golembiowski Date: Sat, 5 Jun 2021 00:15:25 -0400 Subject: [PATCH 6/6] Haphazardly applying #[cfg(unix)] in regions where it looks like it has to go. --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c292e5a..8a226a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,8 @@ extern crate winapi; use std::io; use std::path::Path; + +#[cfg(target_os = "unix")] use std::os::unix::fs::PermissionsExt; /// Returns `true` if there is a file at the given path and it is /// executable. Returns `false` otherwise. @@ -86,6 +88,7 @@ pub trait IsExecutable { /// current run-level is permitted to execute it. /// /// See the module documentation for details. +#[cfg(unix)] pub fn is_permitted

(path: P) -> Result<::std::path::PathBuf, io::Error> where P: AsRef @@ -96,6 +99,7 @@ where /// An extension trait for `std::fs::Path` providing an `is_permitted` method. /// /// See the module documentation for examples. +#[cfg(unix)] pub trait IsPermitted { /// Returns `Result` that describes if a particular file /// exists at the given path and the run-level of the current context meets