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}