use anyhow::anyhow; use std::io::{Read, Write}; use crate::compression_engine::{CompressionType, get_compression_engine}; use crate::common::is_binary::is_binary; use crate::config; use clap::Command; use std::path::PathBuf; use std::str::FromStr; use is_terminal::IsTerminal; pub fn mode_get( cmd: &mut Command, settings: &config::Settings, _config: &config::Config, ids: &mut Vec, tags: &mut Vec, conn: &mut rusqlite::Connection, data_path: PathBuf, ) -> anyhow::Result<()> { if !ids.is_empty() && !tags.is_empty() { cmd.error(clap::error::ErrorKind::InvalidValue, "Both ID and tags given, you must supply exactly one ID or at least one tag when using --get").exit(); } else if ids.len() > 1 { cmd.error(clap::error::ErrorKind::InvalidValue, "More than one ID given, you must supply exactly one ID or at least one tag when using --get").exit(); } let mut meta: std::collections::HashMap = std::collections::HashMap::new(); for item in settings.meta.iter() { let item = item.clone(); meta.insert(item.key, item.value); } let item_maybe = match tags.is_empty() && meta.is_empty() { true => match ids.iter().next() { Some(item_id) => crate::db::get_item(conn, *item_id)?, None => crate::db::get_item_last(conn)?, }, false => crate::db::get_item_matching(conn, tags, &meta)?, }; if let Some(item) = item_maybe { let item_id = item.id.ok_or_else(|| anyhow!("Item missing ID"))?; // Validate that item ID is positive to prevent path traversal issues if item_id <= 0 { return Err(anyhow!("Invalid item ID: {}", item_id)); } let mut item_path = data_path.clone(); item_path.push(item_id.to_string()); // Determine if we should detect binary data let mut detect_binary = !settings.force && std::io::stdout().is_terminal(); // If we're detecting binary and there's binary metadata, check it if detect_binary { let item_meta = crate::db::get_item_meta(conn, &item)?; let binary_meta = item_meta.into_iter().find(|meta| meta.name == "binary"); if let Some(binary_meta) = binary_meta { if binary_meta.value == "false" { // If metadata says it's not binary, don't detect detect_binary = false; } else if binary_meta.value == "true" { // If metadata says it's binary, error immediately return Err(anyhow!("Refusing to output binary data to TTY, use --force to override")); } } } let compression_type = CompressionType::from_str(&item.compression)?; let compression_engine = get_compression_engine(compression_type)?; // If we need to detect binary, read first 4KB and check if detect_binary { // Open the file through compression engine to read first 4KB let mut reader = compression_engine.open(item_path.clone())?; let mut buffer = [0u8; 4096]; let bytes_read = reader.read(&mut buffer)?; // Check if this data is binary if is_binary(&buffer[..bytes_read]) { return Err(anyhow!("Refusing to output binary data to TTY, use --force to override")); } // If not binary, output the data we've read std::io::stdout().write_all(&buffer[..bytes_read])?; // Continue reading and outputting the rest of the data let mut stdout = std::io::stdout(); std::io::copy(&mut reader, &mut stdout)?; } else { // No binary detection needed, just output the data compression_engine.cat(item_path.clone())?; } Ok(()) } else { Err(anyhow!("Unable to find matching item in database")) } }