use std::io;
use std::path::Path;
pub fn secure_file_perms(path: &Path) -> io::Result<()> {
crate::platform::set_mode(path, 0o600)
}
pub fn secure_dir_perms(path: &Path) -> io::Result<()> {
crate::platform::set_mode(path, 0o700)
}
pub fn ensure_file_private(path: &Path) -> io::Result<Option<u32>> {
crate::platform::ensure_private(path, 0o600)
}
pub fn write_private(path: &Path, contents: &[u8]) -> io::Result<()> {
crate::platform::write_with_mode(path, contents, 0o600)
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(unix)]
fn mode_of(path: &Path) -> u32 {
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(path).unwrap().permissions().mode() & 0o777
}
#[cfg(unix)]
fn set_mode(path: &Path, mode: u32) {
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode)).unwrap();
}
#[cfg(windows)]
fn env_lock() -> std::sync::MutexGuard<'static, ()> {
crate::platform::ENV_LOCK.lock().expect("env lock")
}
#[test]
fn write_private_creates_an_owner_only_file_with_the_content() {
#[cfg(windows)]
let _env = env_lock();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("secret");
write_private(&path, b"the key").unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"the key");
#[cfg(unix)]
assert_eq!(mode_of(&path), 0o600);
}
#[test]
fn write_private_retightens_an_existing_permissive_file() {
#[cfg(windows)]
let _env = env_lock();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("secret");
std::fs::write(&path, b"old").unwrap();
#[cfg(unix)]
set_mode(&path, 0o644);
write_private(&path, b"new").unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"new");
#[cfg(unix)]
assert_eq!(mode_of(&path), 0o600);
}
#[test]
fn write_private_propagates_an_unwritable_path() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("no-such-dir").join("secret");
assert!(write_private(&path, b"x").is_err());
}
#[test]
fn secure_file_perms_restricts_on_unix_and_succeeds_everywhere() {
#[cfg(windows)]
let _env = env_lock();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("secret");
std::fs::write(&path, b"x").unwrap();
#[cfg(unix)]
set_mode(&path, 0o644);
secure_file_perms(&path).unwrap();
#[cfg(unix)]
assert_eq!(mode_of(&path), 0o600);
}
#[test]
fn secure_dir_perms_restricts_on_unix_and_succeeds_everywhere() {
#[cfg(windows)]
let _env = env_lock();
let dir = tempfile::tempdir().unwrap();
let sub = dir.path().join("d");
std::fs::create_dir(&sub).unwrap();
#[cfg(unix)]
set_mode(&sub, 0o755);
secure_dir_perms(&sub).unwrap();
#[cfg(unix)]
assert_eq!(mode_of(&sub), 0o700);
}
#[test]
fn secure_file_perms_missing_path_behavior() {
#[cfg(windows)]
let _env = env_lock();
let dir = tempfile::tempdir().unwrap();
let missing = dir.path().join("nope");
let result = secure_file_perms(&missing);
#[cfg(any(unix, windows))]
assert!(result.is_err());
#[cfg(not(any(unix, windows)))]
assert!(result.is_ok());
}
#[test]
fn ensure_file_private_tightens_permissive_file_on_unix() {
#[cfg(windows)]
let _env = env_lock();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("cfg");
std::fs::write(&path, b"x").unwrap();
#[cfg(unix)]
set_mode(&path, 0o644);
let previous = ensure_file_private(&path).unwrap();
#[cfg(unix)]
{
assert_eq!(previous, Some(0o100644));
assert_eq!(mode_of(&path), 0o600);
}
#[cfg(not(unix))]
assert_eq!(previous, None);
}
#[test]
fn ensure_file_private_leaves_private_file_untouched() {
#[cfg(windows)]
let _env = env_lock();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("cfg");
std::fs::write(&path, b"x").unwrap();
#[cfg(unix)]
set_mode(&path, 0o600);
assert_eq!(ensure_file_private(&path).unwrap(), None);
#[cfg(unix)]
assert_eq!(mode_of(&path), 0o600);
}
#[test]
fn ensure_file_private_is_noop_for_missing_path() {
let dir = tempfile::tempdir().unwrap();
let missing = dir.path().join("nope");
assert_eq!(ensure_file_private(&missing).unwrap(), None);
}
}