This commit is contained in:
2026-05-14 20:05:17 -05:00
parent 4dd0c79b20
commit b6124537f9
3 changed files with 17 additions and 6 deletions
+2 -2
View File
@@ -17,7 +17,7 @@ enum ArchiveInfo {
} }
fn archive_info(data: &[u8]) -> Result<ArchiveInfo, DrawerError> { fn archive_info(data: &[u8]) -> Result<ArchiveInfo, DrawerError> {
let mut archive = open_archive(data); let mut archive = archive_reader_for(data);
let mut entries = archive.entries().map_err(|_| DrawerError::UntarFailed)?; let mut entries = archive.entries().map_err(|_| DrawerError::UntarFailed)?;
let first = entries.next() let first = entries.next()
@@ -31,7 +31,7 @@ fn archive_info(data: &[u8]) -> Result<ArchiveInfo, DrawerError> {
} }
} }
let mut archive = open_archive(data); let mut archive = archive_reader_for(data);
let file_count = archive.entries() let file_count = archive.entries()
.map_err(|_| DrawerError::UntarFailed)? .map_err(|_| DrawerError::UntarFailed)?
.filter_map(|e| e.ok()) .filter_map(|e| e.ok())
+3 -3
View File
@@ -15,7 +15,7 @@ pub struct OpenOperation {
} }
fn single_file_entry(data: &[u8]) -> Option<PathBuf> { fn single_file_entry(data: &[u8]) -> Option<PathBuf> {
let mut archive = open_archive(data); let mut archive = archive_reader_for(data);
let mut entries = archive.entries().ok()?; let mut entries = archive.entries().ok()?;
let entry = entries.next()?.ok()?; let entry = entries.next()?.ok()?;
if !entry.header().entry_type().is_file() { if !entry.header().entry_type().is_file() {
@@ -28,7 +28,7 @@ fn single_file_entry(data: &[u8]) -> Option<PathBuf> {
} }
fn unpack_single_file(data: &[u8], target: &Path) -> Result<(), DrawerError> { fn unpack_single_file(data: &[u8], target: &Path) -> Result<(), DrawerError> {
let mut archive = open_archive(data); let mut archive = archive_reader_for(data);
let mut entry = archive.entries() let mut entry = archive.entries()
.map_err(|_| DrawerError::UntarFailed)? .map_err(|_| DrawerError::UntarFailed)?
.next() .next()
@@ -39,7 +39,7 @@ fn unpack_single_file(data: &[u8], target: &Path) -> Result<(), DrawerError> {
} }
fn unpack_directory(data: &[u8], target: &Path) -> Result<(), DrawerError> { fn unpack_directory(data: &[u8], target: &Path) -> Result<(), DrawerError> {
let mut archive = open_archive(data); let mut archive = archive_reader_for(data);
archive.unpack(target).map_err(|_| DrawerError::UntarFailed)?; archive.unpack(target).map_err(|_| DrawerError::UntarFailed)?;
Ok(()) Ok(())
} }
+12 -1
View File
@@ -4,6 +4,7 @@ use age::ssh::Identity;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use crate::error::DrawerError; use crate::error::DrawerError;
/// Returns the parent directory of `p`, or `"."` if `p` has no parent or an empty parent.
pub fn parent_or_dot(p: &Path) -> &Path { pub fn parent_or_dot(p: &Path) -> &Path {
match p.parent() { match p.parent() {
None => Path::new("."), None => Path::new("."),
@@ -12,10 +13,12 @@ pub fn parent_or_dot(p: &Path) -> &Path {
} }
} }
pub fn open_archive(data: &[u8]) -> tar::Archive<GzDecoder<std::io::Cursor<&[u8]>>> { /// Wraps `data` in a gzip-decompressing tar archive reader.
pub fn archive_reader_for(data: &[u8]) -> tar::Archive<GzDecoder<std::io::Cursor<&[u8]>>> {
tar::Archive::new(GzDecoder::new(std::io::Cursor::new(data))) tar::Archive::new(GzDecoder::new(std::io::Cursor::new(data)))
} }
/// Resolves the key path from the explicit `-i` argument or the `$DRAWER_KEY` environment variable.
pub fn key_path(key: &Option<PathBuf>) -> Result<PathBuf, DrawerError> { pub fn key_path(key: &Option<PathBuf>) -> Result<PathBuf, DrawerError> {
if let Some(k) = key { if let Some(k) = key {
return Ok(k.clone()); return Ok(k.clone());
@@ -24,6 +27,7 @@ pub fn key_path(key: &Option<PathBuf>) -> Result<PathBuf, DrawerError> {
Ok(PathBuf::from(val)) Ok(PathBuf::from(val))
} }
/// Loads the SSH identity from disk, returning both the resolved key path and the parsed identity.
pub fn identity(key: &Option<PathBuf>, drawer_file: &Path) -> Result<(PathBuf, Identity), DrawerError> { pub fn identity(key: &Option<PathBuf>, drawer_file: &Path) -> Result<(PathBuf, Identity), DrawerError> {
let kp = key_path(key)?; let kp = key_path(key)?;
let file = std::fs::File::open(&kp) let file = std::fs::File::open(&kp)
@@ -34,6 +38,7 @@ pub fn identity(key: &Option<PathBuf>, drawer_file: &Path) -> Result<(PathBuf, I
Ok((kp, id)) Ok((kp, id))
} }
/// Decrypts `encrypted` using the SSH key, returning the plaintext bytes.
pub fn decrypt(encrypted: Vec<u8>, key: &Option<PathBuf>, drawer_file: &Path) -> Result<Vec<u8>, DrawerError> { pub fn decrypt(encrypted: Vec<u8>, key: &Option<PathBuf>, drawer_file: &Path) -> Result<Vec<u8>, DrawerError> {
let (kp, id) = identity(key, drawer_file)?; let (kp, id) = identity(key, drawer_file)?;
let decryptor = age::Decryptor::new(encrypted.as_slice()) let decryptor = age::Decryptor::new(encrypted.as_slice())
@@ -46,6 +51,7 @@ pub fn decrypt(encrypted: Vec<u8>, key: &Option<PathBuf>, drawer_file: &Path) ->
Ok(decrypted) Ok(decrypted)
} }
/// Encrypts `buf` to the SSH public key derived from the identity, returning the ciphertext.
pub fn encrypt(buf: &[u8], key: &Option<PathBuf>, drawer_file: &Path) -> Result<Vec<u8>, DrawerError> { pub fn encrypt(buf: &[u8], key: &Option<PathBuf>, drawer_file: &Path) -> Result<Vec<u8>, DrawerError> {
let (_, id) = identity(key, drawer_file)?; let (_, id) = identity(key, drawer_file)?;
let recipient = age::ssh::Recipient::try_from(id) let recipient = age::ssh::Recipient::try_from(id)
@@ -61,6 +67,8 @@ pub fn encrypt(buf: &[u8], key: &Option<PathBuf>, drawer_file: &Path) -> Result<
Ok(encrypted) Ok(encrypted)
} }
/// Returns the default open/close target path: the single file's name for single-file archives,
/// or the drawer file's stem for directory archives.
pub fn default_target(drawer_file: &Path, single_file: Option<&Path>) -> PathBuf { pub fn default_target(drawer_file: &Path, single_file: Option<&Path>) -> PathBuf {
match single_file { match single_file {
Some(p) => PathBuf::from(p.file_name().unwrap_or_default()), Some(p) => PathBuf::from(p.file_name().unwrap_or_default()),
@@ -68,6 +76,7 @@ pub fn default_target(drawer_file: &Path, single_file: Option<&Path>) -> PathBuf
} }
} }
/// Errors if `drawer_file` does not have the `.drawer` extension.
pub fn validate_drawer_extension(drawer_file: &Path) -> Result<(), DrawerError> { pub fn validate_drawer_extension(drawer_file: &Path) -> Result<(), DrawerError> {
if drawer_file.extension().and_then(|e| e.to_str()) != Some("drawer") { if drawer_file.extension().and_then(|e| e.to_str()) != Some("drawer") {
return Err(DrawerError::DrawerFileInvalidExtension(drawer_file.to_path_buf())); return Err(DrawerError::DrawerFileInvalidExtension(drawer_file.to_path_buf()));
@@ -75,6 +84,7 @@ pub fn validate_drawer_extension(drawer_file: &Path) -> Result<(), DrawerError>
Ok(()) Ok(())
} }
/// Errors if `drawer_file` does not exist on disk.
pub fn validate_drawer_file_exists(drawer_file: &Path) -> Result<(), DrawerError> { pub fn validate_drawer_file_exists(drawer_file: &Path) -> Result<(), DrawerError> {
if !drawer_file.exists() { if !drawer_file.exists() {
return Err(DrawerError::DrawerFileNotFound(drawer_file.to_path_buf())); return Err(DrawerError::DrawerFileNotFound(drawer_file.to_path_buf()));
@@ -82,6 +92,7 @@ pub fn validate_drawer_file_exists(drawer_file: &Path) -> Result<(), DrawerError
Ok(()) Ok(())
} }
/// Errors if the key cannot be loaded as a valid SSH identity usable for age encryption.
pub fn validate_ssh_key(key: &Option<PathBuf>) -> Result<(), DrawerError> { pub fn validate_ssh_key(key: &Option<PathBuf>) -> Result<(), DrawerError> {
let kp = key_path(key)?; let kp = key_path(key)?;
let file = std::fs::File::open(&kp) let file = std::fs::File::open(&kp)