diff --git a/plans/phase8.md b/plans/phase8.md new file mode 100644 index 0000000..c93b772 --- /dev/null +++ b/plans/phase8.md @@ -0,0 +1,22 @@ +# Phase 8 + +Another quality of life feature, I want to add a fourth command: + +`drawer info foo.drawer` + +This command will take a drawer file (as well as the -i flag for the key that open / close take) and print, to stdout, +some information about it. + +## Folder drawers +Print something like this: +``` +foo.drawer (folder, 3 files) +``` +Be sure to include files in subfolders in the archive. + +## Single file drawers +Print something like this: +``` +foo.drawer (file, 47872 bytes) +``` +The file size printed should be the uncompressed file size. \ No newline at end of file diff --git a/src/parsing.rs b/src/parsing.rs index ed4253f..bd2f508 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -15,6 +15,7 @@ pub enum OperationType { Open, Close, Key, + Info, } #[derive(Debug, PartialEq)] @@ -60,6 +61,12 @@ enum Command { Key { filename: PathBuf, }, + /// Show information about a drawer file + Info { + drawer_file: PathBuf, + #[arg(short = 'i')] + key: Option, + }, } pub fn parse_args(args: I) -> Result @@ -100,6 +107,16 @@ where force: false, }) } + Command::Info { drawer_file, key } => { + let key = resolve_key(key); + Ok(DrawerOperation { + operation_type: OperationType::Info, + drawer_file, + key, + target_path: None, + force: false, + }) + } } } @@ -110,6 +127,39 @@ fn resolve_key(key: Option) -> KeyType { EnvVar } +enum ArchiveInfo { + SingleFile { size: u64 }, + Folder { file_count: usize }, +} + +fn archive_info(data: &[u8]) -> Result { + let cursor = std::io::Cursor::new(data); + let gz = GzDecoder::new(cursor); + let mut archive = tar::Archive::new(gz); + let mut entries = archive.entries().map_err(|_| DrawerError::UntarFailed)?; + + let first = entries.next() + .ok_or(DrawerError::UntarFailed)? + .map_err(|_| DrawerError::UntarFailed)?; + + if first.header().entry_type().is_file() { + let size = first.header().size().map_err(|_| DrawerError::UntarFailed)?; + if entries.next().is_none() { + return Ok(ArchiveInfo::SingleFile { size }); + } + } + + let cursor = std::io::Cursor::new(data); + let gz = GzDecoder::new(cursor); + let mut archive = tar::Archive::new(gz); + let file_count = archive.entries() + .map_err(|_| DrawerError::UntarFailed)? + .filter_map(|e| e.ok()) + .filter(|e| e.header().entry_type().is_file()) + .count(); + Ok(ArchiveInfo::Folder { file_count }) +} + fn single_file_entry(data: &[u8]) -> Option { let cursor = std::io::Cursor::new(data); let gz = GzDecoder::new(cursor); @@ -142,6 +192,7 @@ impl DrawerOperation { OperationType::Open => self.perform_open(), OperationType::Close => self.perform_close(), OperationType::Key => self.perform_key(), + OperationType::Info => self.perform_info(), } } @@ -264,16 +315,14 @@ impl DrawerOperation { self.validate_key()?; } match self.operation_type { - OperationType::Open => self.validate_open(), + OperationType::Open | OperationType::Info => self.validate_drawer_file(), OperationType::Close => self.validate_close(), OperationType::Key => self.validate_key_gen(), } } fn validate_key_gen(&self) -> Result<(), DrawerError> { - if self.drawer_file.exists() { - return Err(DrawerError::KeyOutputFailed(self.drawer_file.clone())); - } + self.validate_drawer_file()?; let parent = match self.drawer_file.parent() { None => Path::new("."), Some(p) => { @@ -312,13 +361,28 @@ impl DrawerOperation { Ok(()) } - fn validate_open(&self) -> Result<(), DrawerError> { + // Ensure the drawer file actually exists + fn validate_drawer_file(&self) -> Result<(), DrawerError> { if !self.drawer_file.exists() { return Err(DrawerError::DrawerFileNotFound(self.drawer_file.clone())); } Ok(()) } + fn perform_info(&self) -> Result<(), DrawerError> { + let encrypted = std::fs::read(&self.drawer_file) + .map_err(|_| DrawerError::DrawerFileNotFound(self.drawer_file.clone()))?; + let decrypted = self.decrypt(encrypted)?; + let info = archive_info(&decrypted)?; + match info { + ArchiveInfo::SingleFile { size } => + println!("{} (file, {} bytes)", self.drawer_file.display(), size), + ArchiveInfo::Folder { file_count } => + println!("{} (folder, {} files)", self.drawer_file.display(), file_count), + } + Ok(()) + } + fn perform_key(&self) -> Result<(), DrawerError> { let private_key = PrivateKey::random(&mut rand::rngs::OsRng, Algorithm::Ed25519) .map_err(|_| DrawerError::KeyGenerateFailed)?;