rix/cmd/
hash.rs

1use crate::cmd::{to_cmd_err, RixSubCommand};
2use crate::hashes;
3use clap::{Arg, ArgAction, ArgMatches, Command};
4
5pub fn cmd() -> RixSubCommand {
6    return RixSubCommand {
7        name: "hash",
8        handler: |args| to_cmd_err(handle_cmd(args)),
9        cmd: |subcommand| {
10            subcommand
11                .about("compute and convert cryptographic hashes")
12                .subcommand(
13                    to_base_cmd("to-base16").about("convert hashes to base-16 representation"),
14                )
15                .subcommand(
16                    to_base_cmd("to-base32")
17                        .about("convert hashes to the Nix base-32 representation"),
18                )
19                .subcommand(
20                    to_base_cmd("to-base64").about("convert hashes to base-64 representation"),
21                )
22                .subcommand(
23                    to_base_cmd("to-sri").about("convert hashes to SRI base-64 representation"),
24                )
25        },
26    };
27}
28
29pub fn handle_cmd(parent_args: &ArgMatches) -> Result<(), String> {
30    if let Some(args) = parent_args.subcommand_matches("to-base16") {
31        handle_to_base_cmd(args, hashes::to_base16)
32    } else if let Some(args) = parent_args.subcommand_matches("to-base32") {
33        handle_to_base_cmd(args, hashes::to_base32)
34    } else if let Some(args) = parent_args.subcommand_matches("to-base64") {
35        handle_to_base_cmd(args, hashes::to_base64)
36    } else if let Some(args) = parent_args.subcommand_matches("to-sri") {
37        handle_to_base_cmd(args, hashes::to_sri)
38    } else {
39        Err("operation not supported".to_owned())
40    }
41}
42
43fn to_base_cmd(name: &'static str) -> Command {
44    Command::new(name)
45        .arg(
46            Arg::new("HASHES")
47                .action(ArgAction::Append)
48                .help("A list of hashes to convert."),
49        )
50        .arg(
51            Arg::new("type")
52                .long("type")
53                .value_name("hash-algo")
54                .value_parser(["md5", "sha1", "sha256", "sha512"])
55                .help("Hash algorithm of input HASHES. Optional as can also be extracted from SRI hash itself."),
56        )
57}
58
59fn handle_to_base_cmd<F>(args: &clap::ArgMatches, to_base_fn: F) -> Result<(), String>
60where
61    F: Fn(&hashes::Hash) -> String,
62{
63    let mut hash_strs = args
64        .get_many::<String>("HASHES")
65        .ok_or("Please specify some hashes.")?;
66    let type_arg = args
67        .get_one::<String>("type")
68        .map(|s| s.as_str())
69        .unwrap_or("sri");
70
71    if let Some(hash_type) = hashes::HashType::from_str(type_arg) {
72        return hash_strs.try_for_each(|hash_str| print_hash(hash_str, hash_type, &to_base_fn));
73    } else if type_arg == "sri" {
74        return sri_to_base(hash_strs, &to_base_fn);
75    }
76    return Err("hash type not supported".to_owned());
77}
78
79fn sri_to_base<'a, F>(
80    mut hash_strs: impl Iterator<Item = &'a String>,
81    to_base_fn: F,
82) -> Result<(), String>
83where
84    F: Fn(&hashes::Hash) -> String,
85{
86    hash_strs.try_for_each(|hash_str| {
87        let (hash_type, hash_str) = hashes::sri_hash_components(hash_str)?;
88        if let Some(hash_type) = hashes::HashType::from_str(hash_type) {
89            return print_hash(hash_str, hash_type, &to_base_fn);
90        }
91        return Err(format!("Hash type '{}' not supported.", hash_type));
92    })
93}
94
95fn print_hash<F>(hash_str: &str, hash_type: hashes::HashType, to_base_fn: F) -> Result<(), String>
96where
97    F: Fn(&hashes::Hash) -> String,
98{
99    hashes::parse(hash_str, hash_type).map(|hash| println!("{}", to_base_fn(&hash)))
100}