use anyhow::{anyhow, Result};
use ascii::AsciiStr;
use clap::{Parser, Subcommand};
use fs_err as fs;
use object::{Object, ObjectSection};
use sbat::{ImageSbat, ImageSbatVec};
use std::path::{Path, PathBuf};
#[derive(Parser)]
#[command(version)]
struct Args {
#[command(subcommand)]
action: Action,
}
#[derive(Subcommand)]
enum Action {
Dump { input: PathBuf },
Validate { input: Vec<PathBuf> },
}
fn read_sbat_section(input: &Path) -> Result<Vec<u8>> {
let data = fs::read(input)?;
let file = object::File::parse(&*data)?;
let section = file
.section_by_name(".sbat")
.ok_or(anyhow!("missing '.sbat' section"))?;
Ok(section.data()?.to_vec())
}
fn dump_sbat(input: &Path) -> Result<()> {
let data = read_sbat_section(input)?;
let sbat = std::str::from_utf8(&data)?;
println!("{sbat}");
Ok(())
}
fn image_sbat_to_table_string(image_sbat: &ImageSbatVec) -> String {
let mut builder = tabled::builder::Builder::default();
builder.set_header([
"component",
"gen",
"vendor",
"package",
"version",
"url",
]);
for entry in image_sbat.entries() {
let component = entry.component;
let vendor = entry.vendor;
let opt_ascii_to_string = |opt: Option<&AsciiStr>| {
opt.map(|s| s.to_string()).unwrap_or_default()
};
builder.push_record([
component.name.to_string(),
component.generation.to_string(),
opt_ascii_to_string(vendor.name),
opt_ascii_to_string(vendor.package_name),
opt_ascii_to_string(vendor.version),
opt_ascii_to_string(vendor.url),
]);
}
builder.build().to_string()
}
fn validate_sbat(inputs: &Vec<PathBuf>) -> Result<()> {
let mut first = true;
for input in inputs {
if first {
first = false;
} else {
println!();
}
println!("{}:", input.display());
let data = read_sbat_section(input)?;
let image_sbat = ImageSbatVec::parse(&data).unwrap();
println!("{}", image_sbat_to_table_string(&image_sbat));
}
Ok(())
}
fn main() -> Result<()> {
let args = Args::parse();
match &args.action {
Action::Dump { input } => dump_sbat(input),
Action::Validate { input } => validate_sbat(input),
}
}
#[cfg(test)]
mod tests {
use super::*;
use sbat::{Component, Entry, Generation, Vendor};
fn ascii(s: &str) -> &AsciiStr {
AsciiStr::from_ascii(s).unwrap()
}
#[test]
fn test_image_sbat_to_table_string() {
let mut image_sbat = ImageSbatVec::new();
image_sbat.push(Entry::new(
Component::new(ascii("pizza"), Generation::new(2).unwrap()),
Vendor {
name: Some(ascii("SomeCorp")),
package_name: Some(ascii("pizza")),
version: Some(ascii("1.2.3")),
url: Some(ascii("https://example.com/somecorp")),
},
));
let expected =
"
+-----------+-----+----------+---------+---------+------------------------------+
| component | gen | vendor | package | version | url |
+-----------+-----+----------+---------+---------+------------------------------+
| pizza | 2 | SomeCorp | pizza | 1.2.3 | https://example.com/somecorp |
+-----------+-----+----------+---------+---------+------------------------------+";
assert_eq!(image_sbat_to_table_string(&image_sbat), expected.trim());
}
}