diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 36dfbbe1e3d..c5c362c5936 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -15,6 +15,7 @@ use std::io::{self, stdin, stdout, BufReader, Read, Write}; use std::iter; use std::path::Path; use uucore::{ + encoding, error::{FromIo, UError, UResult, USimpleError}, format_usage, help_about, help_section, help_usage, show, sum::{ @@ -44,6 +45,13 @@ enum CkSumError { RawMultipleFiles, } +#[derive(Debug, PartialEq)] +enum OutputFormat { + Hexadecimal, + Raw, + Base64, +} + impl UError for CkSumError { fn code(&self) -> i32 { match self { @@ -138,7 +146,7 @@ struct Options { output_bits: usize, untagged: bool, length: Option, - raw: bool, + output_format: OutputFormat, } /// Calculate checksum @@ -153,7 +161,7 @@ where I: Iterator, { let files: Vec<_> = files.collect(); - if options.raw && files.len() > 1 { + if options.output_format == OutputFormat::Raw && files.len() > 1 { return Err(Box::new(CkSumError::RawMultipleFiles)); } @@ -177,7 +185,7 @@ where }; Box::new(file_buf) as Box }); - let (sum, sz) = digest_read(&mut options.digest, &mut file, options.output_bits) + let (sum_hex, sz) = digest_read(&mut options.digest, &mut file, options.output_bits) .map_err_context(|| "failed to read input".to_string())?; if filename.is_dir() { show!(USimpleError::new( @@ -186,17 +194,25 @@ where )); continue; } - if options.raw { - let bytes = match options.algo_name { - ALGORITHM_OPTIONS_CRC => sum.parse::().unwrap().to_be_bytes().to_vec(), - ALGORITHM_OPTIONS_SYSV | ALGORITHM_OPTIONS_BSD => { - sum.parse::().unwrap().to_be_bytes().to_vec() - } - _ => decode(sum).unwrap(), - }; - stdout().write_all(&bytes)?; - return Ok(()); - } + let sum = match options.output_format { + OutputFormat::Raw => { + let bytes = match options.algo_name { + ALGORITHM_OPTIONS_CRC => sum_hex.parse::().unwrap().to_be_bytes().to_vec(), + ALGORITHM_OPTIONS_SYSV | ALGORITHM_OPTIONS_BSD => { + sum_hex.parse::().unwrap().to_be_bytes().to_vec() + } + _ => decode(sum_hex).unwrap(), + }; + // Cannot handle multiple files anyway, output immediately. + stdout().write_all(&bytes)?; + return Ok(()); + } + OutputFormat::Hexadecimal => sum_hex, + OutputFormat::Base64 => match options.algo_name { + ALGORITHM_OPTIONS_CRC | ALGORITHM_OPTIONS_SYSV | ALGORITHM_OPTIONS_BSD => sum_hex, + _ => encoding::encode(encoding::Format::Base64, &decode(sum_hex).unwrap()).unwrap(), + }, + }; // The BSD checksum output is 5 digit integer let bsd_width = 5; match (options.algo_name, not_file) { @@ -286,8 +302,10 @@ mod options { pub const ALGORITHM: &str = "algorithm"; pub const FILE: &str = "file"; pub const UNTAGGED: &str = "untagged"; + pub const TAG: &str = "tag"; pub const LENGTH: &str = "length"; pub const RAW: &str = "raw"; + pub const BASE64: &str = "base64"; } #[uucore::main] @@ -342,13 +360,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (name, algo, bits) = detect_algo(algo_name, length); + let output_format = if matches.get_flag(options::RAW) { + OutputFormat::Raw + } else if matches.get_flag(options::BASE64) { + OutputFormat::Base64 + } else { + OutputFormat::Hexadecimal + }; + let opts = Options { algo_name: name, digest: algo, output_bits: bits, length, untagged: matches.get_flag(options::UNTAGGED), - raw: matches.get_flag(options::RAW), + output_format, }; match matches.get_many::(options::FILE) { @@ -365,6 +391,7 @@ pub fn uu_app() -> Command { .about(ABOUT) .override_usage(format_usage(USAGE)) .infer_long_args(true) + .args_override_self(true) .arg( Arg::new(options::FILE) .hide(true) @@ -395,6 +422,13 @@ pub fn uu_app() -> Command { Arg::new(options::UNTAGGED) .long(options::UNTAGGED) .help("create a reversed style checksum, without digest type") + .action(ArgAction::SetTrue) + .overrides_with(options::TAG), + ) + .arg( + Arg::new(options::TAG) + .long(options::TAG) + .help("create a BSD style checksum, undo --untagged (default)") .action(ArgAction::SetTrue), ) .arg( @@ -411,5 +445,14 @@ pub fn uu_app() -> Command { .help("emit a raw binary digest, not hexadecimal") .action(ArgAction::SetTrue), ) + .arg( + Arg::new(options::BASE64) + .long(options::BASE64) + .help("emit a base64 digest, not hexadecimal") + .action(ArgAction::SetTrue) + // Even though this could easily just override an earlier '--raw', + // GNU cksum does not permit these flags to be combined: + .conflicts_with(options::RAW), + ) .after_help(AFTER_HELP) } diff --git a/src/uucore/src/lib/features/encoding.rs b/src/uucore/src/lib/features/encoding.rs index 0ed54839dc0..dd89c1f5201 100644 --- a/src/uucore/src/lib/features/encoding.rs +++ b/src/uucore/src/lib/features/encoding.rs @@ -23,6 +23,7 @@ pub enum DecodeError { Io(#[from] io::Error), } +#[derive(Debug)] pub enum EncodeError { Z85InputLenNotMultipleOf4, InvalidInput, diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 464de947479..12c54d610a1 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -128,6 +128,29 @@ fn test_stdin_larger_than_128_bytes() { assert_eq!(bytes_cnt, 2058); } +#[test] +fn test_repeated_flags() { + new_ucmd!() + .arg("-a") + .arg("sha1") + .arg("--algo=sha256") + .arg("-a=md5") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture("md5_single_file.expected"); +} + +#[test] +fn test_tag_after_untagged() { + new_ucmd!() + .arg("--untagged") + .arg("--tag") + .arg("-a=md5") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture("md5_single_file.expected"); +} + #[test] fn test_algorithm_single_file() { for algo in ALGOS { @@ -208,6 +231,17 @@ fn test_untagged_algorithm_single_file() { } } +#[test] +fn test_untagged_algorithm_after_tag() { + new_ucmd!() + .arg("--tag") + .arg("--untagged") + .arg("--algorithm=md5") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture("untagged/md5_single_file.expected"); +} + #[test] fn test_untagged_algorithm_multiple_files() { for algo in ALGOS { @@ -291,6 +325,20 @@ fn test_length_is_zero() { .stdout_is_fixture("length_is_zero.expected"); } +#[test] +fn test_length_repeated() { + new_ucmd!() + .arg("--length=10") + .arg("--length=123456") + .arg("--length=0") + .arg("--algorithm=blake2b") + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds() + .no_stderr() + .stdout_is_fixture("length_is_zero.expected"); +} + #[test] fn test_raw_single_file() { for algo in ALGOS { @@ -315,6 +363,43 @@ fn test_raw_multiple_files() { .code_is(1); } +#[test] +fn test_base64_raw_conflicts() { + new_ucmd!() + .arg("--base64") + .arg("--raw") + .arg("lorem_ipsum.txt") + .fails() + .no_stdout() + .stderr_contains("--base64") + .stderr_contains("cannot be used with") + .stderr_contains("--raw"); +} + +#[test] +fn test_base64_single_file() { + for algo in ALGOS { + new_ucmd!() + .arg("--base64") + .arg("lorem_ipsum.txt") + .arg(format!("--algorithm={algo}")) + .succeeds() + .no_stderr() + .stdout_is_fixture_bytes(format!("base64/{algo}_single_file.expected")); + } +} +#[test] +fn test_base64_multiple_files() { + new_ucmd!() + .arg("--base64") + .arg("--algorithm=md5") + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds() + .no_stderr() + .stdout_is_fixture_bytes(format!("base64/md5_multiple_files.expected")); +} + #[test] fn test_fail_on_folder() { let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/fixtures/cksum/base64/blake2b_single_file.expected b/tests/fixtures/cksum/base64/blake2b_single_file.expected new file mode 100644 index 00000000000..18bec38f017 --- /dev/null +++ b/tests/fixtures/cksum/base64/blake2b_single_file.expected @@ -0,0 +1 @@ +BLAKE2b (lorem_ipsum.txt) = DpegkYnlYMN4nAv/HwIBZoYe+FfR+/5FdN4YQuPAbKu5V15K9jCaFmFYwrQI08A4wbSdgos1FYFCzcA5bRGVww== diff --git a/tests/fixtures/cksum/base64/bsd_single_file.expected b/tests/fixtures/cksum/base64/bsd_single_file.expected new file mode 100644 index 00000000000..293ada3bd61 --- /dev/null +++ b/tests/fixtures/cksum/base64/bsd_single_file.expected @@ -0,0 +1 @@ +08109 1 lorem_ipsum.txt diff --git a/tests/fixtures/cksum/base64/crc_single_file.expected b/tests/fixtures/cksum/base64/crc_single_file.expected new file mode 100644 index 00000000000..e9fc1ca7cf4 --- /dev/null +++ b/tests/fixtures/cksum/base64/crc_single_file.expected @@ -0,0 +1 @@ +378294376 772 lorem_ipsum.txt diff --git a/tests/fixtures/cksum/base64/md5_multiple_files.expected b/tests/fixtures/cksum/base64/md5_multiple_files.expected new file mode 100644 index 00000000000..e7f844b9ac7 --- /dev/null +++ b/tests/fixtures/cksum/base64/md5_multiple_files.expected @@ -0,0 +1,2 @@ +MD5 (lorem_ipsum.txt) = zXJGkPfcYXdd+sQApx8sqg== +MD5 (alice_in_wonderland.txt) = 9vpwM+FhZqlYmqHAOI/9WA== diff --git a/tests/fixtures/cksum/base64/md5_single_file.expected b/tests/fixtures/cksum/base64/md5_single_file.expected new file mode 100644 index 00000000000..8a12f79278c --- /dev/null +++ b/tests/fixtures/cksum/base64/md5_single_file.expected @@ -0,0 +1 @@ +MD5 (lorem_ipsum.txt) = zXJGkPfcYXdd+sQApx8sqg== diff --git a/tests/fixtures/cksum/base64/sha1_single_file.expected b/tests/fixtures/cksum/base64/sha1_single_file.expected new file mode 100644 index 00000000000..b12608e77c7 --- /dev/null +++ b/tests/fixtures/cksum/base64/sha1_single_file.expected @@ -0,0 +1 @@ +SHA1 (lorem_ipsum.txt) = qx3QuuHYiDo9GKZt5q+9KCUs++8= diff --git a/tests/fixtures/cksum/base64/sha224_single_file.expected b/tests/fixtures/cksum/base64/sha224_single_file.expected new file mode 100644 index 00000000000..69d85abdb86 --- /dev/null +++ b/tests/fixtures/cksum/base64/sha224_single_file.expected @@ -0,0 +1 @@ +SHA224 (lorem_ipsum.txt) = PeZvvK0QbhtAqzkb5WxR0gB+sfnGVdD04pv8AQ== diff --git a/tests/fixtures/cksum/base64/sha256_single_file.expected b/tests/fixtures/cksum/base64/sha256_single_file.expected new file mode 100644 index 00000000000..d9a8814a30b --- /dev/null +++ b/tests/fixtures/cksum/base64/sha256_single_file.expected @@ -0,0 +1 @@ +SHA256 (lorem_ipsum.txt) = 98QgUBxQ4AswklAQDWfqXpEJgVNrRYL+nENb2Ss/HwI= diff --git a/tests/fixtures/cksum/base64/sha384_single_file.expected b/tests/fixtures/cksum/base64/sha384_single_file.expected new file mode 100644 index 00000000000..8a8333d3553 --- /dev/null +++ b/tests/fixtures/cksum/base64/sha384_single_file.expected @@ -0,0 +1 @@ +SHA384 (lorem_ipsum.txt) = S+S5Cg0NMpZpkpIQGfJKvIJNz7ixxAgQLx9niPuAupqaTFp7V1ozU6kKjucZSB3L diff --git a/tests/fixtures/cksum/base64/sha512_single_file.expected b/tests/fixtures/cksum/base64/sha512_single_file.expected new file mode 100644 index 00000000000..da36c4295fb --- /dev/null +++ b/tests/fixtures/cksum/base64/sha512_single_file.expected @@ -0,0 +1 @@ +SHA512 (lorem_ipsum.txt) = llRkqyVWqtWOvHPYmtIh5Vl5dSnsr8D0ZsEXlc/21uLGD5agfFQs/R9Cbl5P4KSKoVZnukQJayE9CBPNA436BQ== diff --git a/tests/fixtures/cksum/base64/sm3_single_file.expected b/tests/fixtures/cksum/base64/sm3_single_file.expected new file mode 100644 index 00000000000..e4ca582c938 --- /dev/null +++ b/tests/fixtures/cksum/base64/sm3_single_file.expected @@ -0,0 +1 @@ +SM3 (lorem_ipsum.txt) = bSlrgF0GC/7SKAjfMI27m0MXeU3U7WdAoQdwp4Jpm8I= diff --git a/tests/fixtures/cksum/base64/sysv_single_file.expected b/tests/fixtures/cksum/base64/sysv_single_file.expected new file mode 100644 index 00000000000..e0f7252cbe8 --- /dev/null +++ b/tests/fixtures/cksum/base64/sysv_single_file.expected @@ -0,0 +1 @@ +6985 2 lorem_ipsum.txt