use std::io::Write;
use std::process::{Command, Stdio};
use zeroize::{Zeroize, Zeroizing};
pub fn try_get_from_pass(key: &str) -> Option<Zeroizing<String>> {
let mut output = Command::new("pass").arg("--").arg(key).output().ok()?;
if !output.status.success() {
output.stdout.zeroize();
return None;
}
let result = std::str::from_utf8(&output.stdout)
.ok()
.map(|s| Zeroizing::new(s.trim().to_string()));
output.stdout.zeroize();
result
}
pub fn get_from_pass(arg: &str) -> Zeroizing<String> {
let mut output = Command::new("pass")
.arg("--")
.arg(arg)
.output()
.expect("Failed to execute 'pass' command");
let key = Zeroizing::new(
std::str::from_utf8(&output.stdout)
.expect("Output from 'pass' command is not valid UTF-8")
.trim()
.to_string(),
);
output.stdout.zeroize();
key
}
pub fn store_in_pass(key: &str, value: &str) -> bool {
if try_get_from_pass(key).is_some() {
return false;
}
let mut child = Command::new("pass")
.arg("insert")
.arg("--echo")
.arg("--")
.arg(key)
.stdin(Stdio::piped())
.spawn()
.expect("Failed to spawn 'pass' command");
child
.stdin
.take()
.expect("Failed to open stdin")
.write_all(value.as_bytes())
.expect("Failed to write to 'pass' stdin");
child.wait().expect("Failed to wait for 'pass' command");
true
}
pub fn force_store_in_pass(key: &str, value: &str) {
let mut child = Command::new("pass")
.arg("insert")
.arg("--echo")
.arg("--force")
.arg("--")
.arg(key)
.stdin(Stdio::piped())
.spawn()
.expect("Failed to spawn 'pass' command");
child
.stdin
.take()
.expect("Failed to open stdin")
.write_all(value.as_bytes())
.expect("Failed to write to 'pass' stdin");
child.wait().expect("Failed to wait for 'pass' command");
}
pub fn insert_to_pass(key: &str, len: u32) -> Zeroizing<String> {
let mut command = Command::new("pass")
.arg("generate")
.arg("-f")
.arg("--no-symbols")
.arg("--")
.arg(key)
.arg(len.to_string())
.output()
.expect("Failed to run 'pass' command");
let output = Zeroizing::new(
std::str::from_utf8(&command.stdout)
.expect("Output from 'pass' command is not valid UTF-8")
.trim()
.to_string(),
);
command.stdout.zeroize();
output
}
pub fn remove_from_pass(key: &str) {
Command::new("pass")
.arg("rm")
.arg("-f")
.arg("--")
.arg(key)
.output()
.expect("Failed to remove key from 'pass'");
}
#[cfg(test)]
mod tests {
fn _insert_test_pass() -> String {
let command = Command::new("pass")
.arg("generate")
.arg("-f")
.arg("--no-symbols")
.arg("test_pass_667667667667")
.arg("16")
.output()
.expect("Failed to run 'pass' command");
let output = std::str::from_utf8(&command.stdout)
.expect("Output from 'pass' command is not valid UTF-8")
.trim()
.to_string();
output
}
use super::*;
#[test]
fn test_try_get_existing_key_returns_some() {
let test_key = "try_get_test_content_42";
insert_to_pass(test_key, 8);
let result = try_get_from_pass(test_key);
assert_eq!(result.unwrap().len(), 8);
Command::new("pass")
.arg("rm")
.arg("-f")
.arg(test_key)
.output()
.expect("cleanup failed");
}
#[test]
fn test_try_get_missing_key_returns_none() {
let result = try_get_from_pass("try_get_test_nonexistent_key_42");
assert_eq!(result, None);
}
#[test]
fn test_get_password() {
_insert_test_pass();
let testpass = get_from_pass("test_pass_667667667667");
assert_eq!(testpass.len(), "v0M6ILl4oe89KgQn".len());
}
#[test]
fn test_insert_to_pass() {
let test_key = "test_insert_pass_12345";
let password_length = 16;
insert_to_pass(test_key, password_length);
let retrieved_pass = get_from_pass(test_key);
assert_eq!(
retrieved_pass.len(),
password_length as usize,
"The retrieved password does not have the expected length."
);
Command::new("pass")
.arg("rm")
.arg("-f")
.arg(test_key)
.output()
.expect("Failed to remove test key from 'pass'");
}
#[test]
fn test_force_store_in_pass_value_is_retrievable() {
let test_key = "store_in_pass_test_retrieve_42";
let test_value = "super_secret_token_abc123";
force_store_in_pass(test_key, test_value);
let result = try_get_from_pass(test_key);
assert_eq!(result.as_ref().map(|s| s.as_str()), Some(test_value));
Command::new("pass")
.arg("rm")
.arg("-f")
.arg(test_key)
.output()
.expect("cleanup failed");
}
#[test]
fn test_force_store_in_pass_overwrites_existing() {
let test_key = "store_in_pass_test_overwrite_42";
force_store_in_pass(test_key, "first_value");
force_store_in_pass(test_key, "second_value");
let result = try_get_from_pass(test_key);
assert_eq!(result.as_ref().map(|s| s.as_str()), Some("second_value"));
Command::new("pass")
.arg("rm")
.arg("-f")
.arg(test_key)
.output()
.expect("cleanup failed");
}
#[test]
fn test_store_in_pass_returns_true_and_stores_value() {
let test_key = "store_in_pass_new_key_42";
let stored = store_in_pass(test_key, "my_token");
assert!(stored);
assert_eq!(
try_get_from_pass(test_key).as_ref().map(|s| s.as_str()),
Some("my_token")
);
Command::new("pass")
.arg("rm")
.arg("-f")
.arg(test_key)
.output()
.expect("cleanup failed");
}
#[test]
fn test_store_in_pass_returns_false_if_key_exists() {
let test_key = "store_in_pass_existing_key_42";
force_store_in_pass(test_key, "original");
let stored = store_in_pass(test_key, "should_not_overwrite");
assert!(!stored);
assert_eq!(
try_get_from_pass(test_key).as_ref().map(|s| s.as_str()),
Some("original")
);
Command::new("pass")
.arg("rm")
.arg("-f")
.arg(test_key)
.output()
.expect("cleanup failed");
}
#[test]
fn test_try_get_flag_like_key_returns_none() {
assert_eq!(try_get_from_pass("--version"), None);
}
#[test]
fn test_store_in_pass_flag_like_key() {
let test_key = "--getfrompass-store-flag";
store_in_pass(test_key, "flag_test_value");
assert_eq!(
try_get_from_pass(test_key).as_ref().map(|s| s.as_str()),
Some("flag_test_value")
);
Command::new("pass")
.arg("rm")
.arg("-f")
.arg(test_key)
.output()
.expect("cleanup failed");
}
#[test]
fn test_force_store_in_pass_flag_like_key() {
let test_key = "--getfrompass-force-flag";
force_store_in_pass(test_key, "flag_force_value");
assert_eq!(
try_get_from_pass(test_key).as_ref().map(|s| s.as_str()),
Some("flag_force_value")
);
Command::new("pass")
.arg("rm")
.arg("-f")
.arg(test_key)
.output()
.expect("cleanup failed");
}
#[test]
fn test_insert_to_pass_flag_like_key() {
let test_key = "--getfrompass-insert-flag";
insert_to_pass(test_key, 8);
assert_eq!(try_get_from_pass(test_key).unwrap().len(), 8);
Command::new("pass")
.arg("rm")
.arg("-f")
.arg(test_key)
.output()
.expect("cleanup failed");
}
#[test]
fn test_remove_from_pass_flag_like_key() {
let test_key = "--getfrompass-remove-flag";
let mut child = Command::new("pass")
.arg("insert")
.arg("--echo")
.arg("--force")
.arg("--")
.arg(test_key)
.stdin(Stdio::piped())
.spawn()
.expect("setup failed");
child
.stdin
.take()
.unwrap()
.write_all(b"to_be_removed")
.unwrap();
child.wait().expect("setup failed");
remove_from_pass(test_key);
let output = Command::new("pass")
.arg("--")
.arg(test_key)
.output()
.unwrap();
assert!(!output.status.success(), "key should have been removed");
}
#[test]
fn test_remove_from_pass() {
let test_key = "removal_test_123565";
insert_to_pass(test_key, 16);
remove_from_pass(test_key);
let output = Command::new("pass")
.arg(test_key)
.output()
.expect("Failed to execute 'pass' command");
assert!(
!output.status.success(),
"Expected the command to fail when attempting to get a removed key, but it succeeded."
);
}
}