malwaredb-client 0.3.4

Client application and library for connecting to MalwareDB.
Documentation
// SPDX-License-Identifier: Apache-2.0

use std::fs::File;
use std::io::Cursor;
use std::path::PathBuf;
use std::process::ExitCode;

use anyhow::{Context, Result};
use clap::{Parser, ValueEnum};

use malwaredb_client::{decode_from_cart, encode_to_cart};

/// `CaRT` action to be performed (excluding display)
#[derive(Clone, ValueEnum, Debug, Eq, PartialEq)]
pub enum Action {
    /// Encode a `CaRT` file
    Encode,

    /// Decode a `CaRT` file
    Decode,
}

/// Arguments for reading a `CaRT` file's metadata and encoding/decoding a `CaRT`.
#[derive(Parser, Clone, Debug, Eq, PartialEq)]
pub struct CartIO {
    /// Action to be performed, or no action to display `CaRT` information
    #[arg(short, long)]
    pub action: Option<Action>,

    /// Output file
    #[arg(short, long, value_hint = clap::ValueHint::FilePath)]
    pub output: Option<PathBuf>,

    /// File to encode or decode
    #[arg(value_name = "FILE", value_hint = clap::ValueHint::FilePath)]
    pub file: PathBuf,
}

impl CartIO {
    pub fn execute(&self) -> Result<ExitCode> {
        if self.output.is_none() != self.action.is_none() {
            eprintln!("Action requires an output, and vise-versa");
            return Ok(ExitCode::FAILURE);
        }

        if self.output.is_none() && self.action.is_none() {
            let mut input_file = File::open(&self.file)?;
            let mut output_buffer = Cursor::new(vec![]);

            return if let Ok((header, footer)) =
                cart_container::unpack_stream(&mut input_file, &mut output_buffer, None)
            {
                let data = output_buffer.into_inner();
                let magic = hex::encode(&data[0..10]);
                println!("First ten bytes: {magic}");
                if let Some(meta) = header {
                    if !meta.is_empty() {
                        println!("Header:");
                    }
                    for (key, value) in meta {
                        println!("{key}: {value}");
                    }
                }
                if let Some(meta) = footer {
                    if !meta.is_empty() {
                        println!("Footer:");
                    }
                    for (key, value) in meta {
                        println!("{key}: {value}");
                    }
                }
                Ok(ExitCode::SUCCESS)
            } else {
                eprintln!("{} is not a CaRT file.", self.file.display());
                Ok(ExitCode::FAILURE)
            };
        }

        let file_contents = std::fs::read(&self.file).context("failed to read file from disk")?;
        match self.action.as_ref().unwrap() {
            Action::Encode => {
                let encoded = encode_to_cart(&file_contents)?;
                std::fs::write(self.output.as_ref().unwrap(), encoded)
                    .context("failed to write CaRT to disk")?;
            }
            Action::Decode => {
                let (decoded, _, _) = decode_from_cart(&file_contents)?;
                std::fs::write(self.output.as_ref().unwrap(), decoded)
                    .context("failed to write decoded file to disk")?;
            }
        }

        Ok(ExitCode::SUCCESS)
    }
}