#![deny(unsafe_code)]
#![warn(
clippy::unwrap_used,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unused_lifetimes,
unused_qualifications
)]
extern crate core;
pub use crate::errors::PassError;
pub use crate::store_entry::{StoreDirectoryIter, StoreDirectoryRef, StoreEntry, StoreFileRef};
use std::collections::HashSet;
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::{env, fs};
mod errors;
pub mod file_io;
mod store_entry;
#[cfg(test)]
mod tests;
mod utils;
pub type Result<T, E = PassError> = core::result::Result<T, E>;
pub const PASSWORD_STORE_DIR_ENV: &str = "PASSWORD_STORE_DIR";
pub fn password_store_dir() -> Result<PathBuf> {
let path = match env::var(PASSWORD_STORE_DIR_ENV) {
Ok(env_var) => Path::new(&env_var).to_path_buf(),
Err(_) => Path::new("~/.password-store").to_path_buf(),
};
Ok(utils::canonicalize_path(&path)?)
}
pub fn list() -> Result<HashSet<StoreEntry>> {
match retrieve("/")? {
StoreEntry::File(file) => Err(PassError::InvalidStoreFormat(
file.path,
"Store root is not a directory but a file".to_string(),
)),
StoreEntry::Directory(dir) => Ok(HashSet::from_iter(dir.iter().cloned())),
}
}
fn inspect_folder(path: impl AsRef<Path>) -> Result<HashSet<StoreEntry>> {
fs::read_dir(path)?
.map(|file| match file {
Err(e) => Err(e),
Ok(file) => Ok((
file.path(),
file.path().extension().unwrap_or_else(|| OsStr::new("")).to_os_string(),
file.file_type()?,
)),
})
.collect::<Result<Vec<_>, _>>()?
.iter()
.filter(|(_, file_extension, file_type)| (file_type.is_file() && file_extension == &OsString::from("gpg") || !file_type.is_file()))
.map(|(path, _, file_type)|
if file_type.is_file() {
Ok(StoreEntry::File(StoreFileRef {
path: path.clone()
}))
} else if file_type.is_dir() {
Ok(StoreEntry::Directory(StoreDirectoryRef{
content: inspect_folder(&path)?,
path: path.clone(),
}))
} else {
Err(PassError::InvalidStoreFormat(
path.clone(),
"File is neither a string nor directory but pass stores can only contain those types of files".to_string())
)
})
.collect()
}
pub fn retrieve(pass_name: &str) -> Result<StoreEntry> {
let pass_name = match pass_name.strip_prefix('/') {
Some(result) => result,
None => pass_name,
};
let dir_path = password_store_dir()?.join(pass_name);
let file_path = password_store_dir()?.join(pass_name.to_string() + ".gpg");
match (dir_path.exists(), file_path.exists()) {
(true, true) => Err(PassError::AmbiguousPassName(pass_name.to_string())),
(false, false) => Err(PassError::EntryNotFound(pass_name.to_string())),
(true, false) => Ok(StoreEntry::Directory(StoreDirectoryRef {
content: inspect_folder(&dir_path)?,
path: dir_path,
})),
(false, true) => Ok(StoreEntry::File(StoreFileRef { path: file_path })),
}
.and_then(|store_entry| {
store_entry.verify()?;
Ok(store_entry)
})
}