From 3d52a89ce3cfaecbabf5ab30e0f7d8a2182b473a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 28 Sep 2025 13:42:04 +0200 Subject: [PATCH 1/2] b2sum: better validation of the args like in tests/cksum/b2sum --- src/uu/cksum/locales/en-US.ftl | 2 + src/uu/cksum/locales/fr-FR.ftl | 2 + src/uu/cksum/src/cksum.rs | 14 +-- src/uu/hashsum/locales/en-US.ftl | 2 + src/uu/hashsum/locales/fr-FR.ftl | 2 + src/uu/hashsum/src/hashsum.rs | 11 +- src/uucore/src/lib/features/checksum.rs | 109 ++++++++++++++++-- tests/by-util/test_cksum.rs | 27 ++++- tests/by-util/test_hashsum.rs | 19 ++- .../cksum/length_larger_than_512.expected | 4 +- 10 files changed, 168 insertions(+), 24 deletions(-) diff --git a/src/uu/cksum/locales/en-US.ftl b/src/uu/cksum/locales/en-US.ftl index 338ae874ce4..98e9f15398e 100644 --- a/src/uu/cksum/locales/en-US.ftl +++ b/src/uu/cksum/locales/en-US.ftl @@ -33,3 +33,5 @@ cksum-help-zero = end each output line with NUL, not newline, and disable file n # Error messages cksum-error-is-directory = { $file }: Is a directory cksum-error-failed-to-read-input = failed to read input +cksum-error-invalid-length = invalid length: { $length } +cksum-error-max-digest-length = maximum digest length for '{ $algorithm }' is { $max_bits } bits diff --git a/src/uu/cksum/locales/fr-FR.ftl b/src/uu/cksum/locales/fr-FR.ftl index 52b4b9ed742..1a3f0a59225 100644 --- a/src/uu/cksum/locales/fr-FR.ftl +++ b/src/uu/cksum/locales/fr-FR.ftl @@ -33,3 +33,5 @@ cksum-help-zero = terminer chaque ligne de sortie avec NUL, pas un saut de ligne # Messages d'erreur cksum-error-is-directory = { $file } : Est un répertoire cksum-error-failed-to-read-input = échec de la lecture de l'entrée +cksum-error-invalid-length = longueur invalide : { $length } +cksum-error-max-digest-length = longueur maximale de condensé pour '{ $algorithm }' est { $max_bits } bits diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 7d595e625ac..fb3d8c23ed2 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -6,7 +6,7 @@ // spell-checker:ignore (ToDO) fname, algo use clap::builder::ValueParser; -use clap::{Arg, ArgAction, Command, value_parser}; +use clap::{Arg, ArgAction, Command}; use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::{BufReader, Read, Write, stdin, stdout}; @@ -15,8 +15,8 @@ use std::path::Path; use uucore::checksum::{ ALGORITHM_OPTIONS_BLAKE2B, ALGORITHM_OPTIONS_BSD, ALGORITHM_OPTIONS_CRC, ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_SYSV, ChecksumError, ChecksumOptions, - ChecksumVerbose, SUPPORTED_ALGORITHMS, calculate_blake2b_length, detect_algo, digest_reader, - perform_checksum_validation, + ChecksumVerbose, SUPPORTED_ALGORITHMS, detect_algo, digest_reader, perform_checksum_validation, + validate_blake2b_length_with_fluent, }; use uucore::translate; @@ -250,12 +250,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } }; - let input_length = matches.get_one::(options::LENGTH); + let input_length = matches.get_one::(options::LENGTH); let length = match input_length { - Some(length) => { + Some(length_str) => { if algo_name == ALGORITHM_OPTIONS_BLAKE2B { - calculate_blake2b_length(*length)? + validate_blake2b_length_with_fluent(length_str, "cksum")? } else { return Err(ChecksumError::LengthOnlyForBlake2b.into()); } @@ -378,7 +378,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::LENGTH) .long(options::LENGTH) - .value_parser(value_parser!(usize)) + .value_parser(ValueParser::string()) .short('l') .help(translate!("cksum-help-length")) .action(ArgAction::Set), diff --git a/src/uu/hashsum/locales/en-US.ftl b/src/uu/hashsum/locales/en-US.ftl index 2001a849181..f4ab69cdcca 100644 --- a/src/uu/hashsum/locales/en-US.ftl +++ b/src/uu/hashsum/locales/en-US.ftl @@ -40,3 +40,5 @@ hashsum-help-b3sum = work with BLAKE3 # Error messages hashsum-error-failed-to-read-input = failed to read input +hashsum-error-invalid-length = invalid length: { $length } +hashsum-error-max-digest-length = maximum digest length for '{ $algorithm }' is { $max_bits } bits diff --git a/src/uu/hashsum/locales/fr-FR.ftl b/src/uu/hashsum/locales/fr-FR.ftl index e612841a56b..7724ab63a09 100644 --- a/src/uu/hashsum/locales/fr-FR.ftl +++ b/src/uu/hashsum/locales/fr-FR.ftl @@ -37,3 +37,5 @@ hashsum-help-b3sum = travailler avec BLAKE3 # Messages d'erreur hashsum-error-failed-to-read-input = échec de la lecture de l'entrée +hashsum-error-invalid-length = longueur invalide : { $length } +hashsum-error-max-digest-length = longueur maximale de condensé pour '{ $algorithm }' est { $max_bits } bits diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index d3de97489b5..e50d8e65d8a 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -7,7 +7,6 @@ use clap::ArgAction; use clap::builder::ValueParser; -use clap::value_parser; use clap::{Arg, ArgMatches, Command}; use std::ffi::{OsStr, OsString}; use std::fs::File; @@ -19,12 +18,12 @@ use uucore::checksum::ChecksumError; use uucore::checksum::ChecksumOptions; use uucore::checksum::ChecksumVerbose; use uucore::checksum::HashAlgorithm; -use uucore::checksum::calculate_blake2b_length; use uucore::checksum::create_sha3; use uucore::checksum::detect_algo; use uucore::checksum::digest_reader; use uucore::checksum::escape_filename; use uucore::checksum::perform_checksum_validation; +use uucore::checksum::validate_blake2b_length; use uucore::error::{FromIo, UResult}; use uucore::format_usage; use uucore::sum::{Digest, Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}; @@ -185,14 +184,14 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { // least somewhat better from a user's perspective. let matches = uucore::clap_localization::handle_clap_result(command, args)?; - let input_length: Option<&usize> = if binary_name == "b2sum" { - matches.get_one::(options::LENGTH) + let input_length: Option<&String> = if binary_name == "b2sum" { + matches.get_one::(options::LENGTH) } else { None }; let length = match input_length { - Some(length) => calculate_blake2b_length(*length)?, + Some(length_str) => validate_blake2b_length(length_str, "hashsum")?, None => None, }; @@ -427,7 +426,7 @@ fn uu_app_opt_length(command: Command) -> Command { command.arg( Arg::new(options::LENGTH) .long(options::LENGTH) - .value_parser(value_parser!(usize)) + .value_parser(ValueParser::string()) .short('l') .help(translate!("hashsum-help-length")) .overrides_with(options::LENGTH) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 4a496e24f1e..6638d613b14 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -17,6 +17,7 @@ use std::{ }; use crate::{ + display::Quotable as DisplayQuotable, error::{FromIo, UError, UResult, USimpleError}, os_str_as_bytes, os_str_from_bytes, quoting_style::{QuotingStyle, locale_aware_escape_name}, @@ -25,7 +26,7 @@ use crate::{ Blake2b, Blake3, Bsd, CRC32B, Crc, Digest, DigestWriter, Md5, Sha1, Sha3_224, Sha3_256, Sha3_384, Sha3_512, Sha224, Sha256, Sha384, Sha512, Shake128, Shake256, Sm3, SysV, }, - util_name, + translate, util_name, }; use thiserror::Error; @@ -1185,22 +1186,47 @@ pub fn digest_reader( } } +/// Validates and calculates the length of the digest from a string input. +/// This function handles very large numbers that might not fit in usize. +pub fn validate_blake2b_length_str(length_str: &str) -> UResult> { + // First try to parse as u128 to handle very large numbers + match length_str.parse::() { + Ok(length_u128) => { + if length_u128 > usize::MAX as u128 { + // For very large numbers, always show the max length error + show_error!("invalid length: '{length_str}'"); + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "maximum digest length for 'BLAKE2b' is 512 bits", + ) + .into()); + } + let length = length_u128 as usize; + calculate_blake2b_length(length) + } + Err(_) => { + show_error!("invalid length: '{length_str}'"); + Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid length").into()) + } + } +} + /// Calculates the length of the digest. pub fn calculate_blake2b_length(length: usize) -> UResult> { match length { 0 => Ok(None), - n if n % 8 != 0 => { - show_error!("invalid length: \u{2018}{length}\u{2019}"); - Err(io::Error::new(io::ErrorKind::InvalidInput, "length is not a multiple of 8").into()) - } n if n > 512 => { - show_error!("invalid length: \u{2018}{length}\u{2019}"); + show_error!("invalid length: '{length}'"); Err(io::Error::new( io::ErrorKind::InvalidInput, - "maximum digest length for \u{2018}BLAKE2b\u{2019} is 512 bits", + "maximum digest length for 'BLAKE2b' is 512 bits", ) .into()) } + n if n % 8 != 0 => { + show_error!("invalid length: '{length}'"); + Err(io::Error::new(io::ErrorKind::InvalidInput, "length is not a multiple of 8").into()) + } n => { // Divide by 8, as our blake2b implementation expects bytes instead of bits. if n == 512 { @@ -1214,6 +1240,75 @@ pub fn calculate_blake2b_length(length: usize) -> UResult> { } } +/// Validate BLAKE2b length with Fluent error messages +/// This function is used by utilities that need localized error messages +pub fn validate_blake2b_length(length_str: &str, utility_name: &str) -> UResult> { + // First try to parse as u128 to handle very large numbers + match length_str.parse::() { + Ok(length_u128) => { + if length_u128 > usize::MAX as u128 { + // For very large numbers, always show the max length error + // Use the original string to avoid precision issues + let error_key = format!("{}-error-invalid-length", utility_name); + let max_key = format!("{}-error-max-digest-length", utility_name); + show_error!( + "{}", + translate!(&error_key, "length" => DisplayQuotable::quote(length_str)) + ); + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + translate!(&max_key, "algorithm" => "BLAKE2b", "max_bits" => 512), + ) + .into()); + } + let length = length_u128 as usize; + match length { + 0 => Ok(None), + n if n > 512 => { + let error_key = format!("{}-error-invalid-length", utility_name); + let max_key = format!("{}-error-max-digest-length", utility_name); + show_error!( + "{}", + translate!(&error_key, "length" => DisplayQuotable::quote(length_str)) + ); + Err(io::Error::new( + io::ErrorKind::InvalidInput, + translate!(&max_key, "algorithm" => "BLAKE2b", "max_bits" => 512), + ) + .into()) + } + n if n % 8 != 0 => { + let error_key = format!("{}-error-invalid-length", utility_name); + show_error!( + "{}", + translate!(&error_key, "length" => DisplayQuotable::quote(length_str)) + ); + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "length is not a multiple of 8", + ) + .into()) + } + n => { + // Divide by 8, as our blake2b implementation expects bytes instead of bits. + if n == 512 { + // When length is 512, it is blake2b's default. + // So, don't show it + Ok(None) + } else { + Ok(Some(n / 8)) + } + } + } + } + Err(_) => { + let error_key = format!("{}-error-invalid-length", utility_name); + show_error!("{}", translate!(&error_key, "length" => length_str.quote())); + Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid length").into()) + } + } +} + pub fn unescape_filename(filename: &[u8]) -> (Vec, &'static str) { let mut unescaped = Vec::with_capacity(filename.len()); let mut byte_iter = filename.iter().peekable(); diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 3e84a2d04e6..2819210d4cb 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (words) asdf algo algos asha mgmt xffname hexa GFYEQ HYQK Yqxb dont +// spell-checker:ignore (words) asdf algo algos asha mgmt xffname hexa GFYEQ HYQK Yqxb dont OFLOW use uutests::at_and_ucmd; use uutests::new_ucmd; @@ -394,6 +394,31 @@ fn test_length_greater_than_512() { .stderr_is_fixture("length_larger_than_512.expected"); } +#[test] +fn test_length_513_shows_max_length_error() { + // Test that length 513 shows "maximum digest length" error before "not multiple of 8" error + new_ucmd!() + .arg("--length=513") + .arg("--algorithm=blake2b") + .arg("/dev/null") + .fails_with_code(1) + .no_stdout() + .stderr_contains("maximum digest length for 'BLAKE2b' is 512 bits"); +} + +#[test] +fn test_length_very_large_number() { + // Test that very large numbers like UINTMAX_OFLOW show proper error messages + new_ucmd!() + .arg("--length=18446744073709551616") + .arg("--algorithm=blake2b") + .arg("/dev/null") + .fails_with_code(1) + .no_stdout() + .stderr_contains("invalid length: '18446744073709551616'") + .stderr_contains("maximum digest length for 'BLAKE2b' is 512 bits"); +} + #[test] fn test_length_is_zero() { new_ucmd!() diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index a138d4e6b7d..dac15e49e67 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -269,7 +269,24 @@ fn test_invalid_b2sum_length_option_too_large() { .ccmd("b2sum") .arg("--length=513") .arg(at.subdir.join("testf")) - .fails_with_code(1); + .fails_with_code(1) + .stderr_contains("maximum digest length for 'BLAKE2b' is 512 bits"); +} + +#[test] +fn test_invalid_b2sum_length_very_large_number() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + + scene + .ccmd("b2sum") + .arg("--length=18446744073709551616") + .arg(at.subdir.join("testf")) + .fails_with_code(1) + .stderr_contains("invalid length: '18446744073709551616'") + .stderr_contains("maximum digest length for 'BLAKE2b' is 512 bits"); } #[test] diff --git a/tests/fixtures/cksum/length_larger_than_512.expected b/tests/fixtures/cksum/length_larger_than_512.expected index 312b6230e9a..8b5d3d4c22a 100644 --- a/tests/fixtures/cksum/length_larger_than_512.expected +++ b/tests/fixtures/cksum/length_larger_than_512.expected @@ -1,2 +1,2 @@ -cksum: invalid length: ‘1024’ -cksum: maximum digest length for ‘BLAKE2b’ is 512 bits +cksum: invalid length: '1024' +cksum: maximum digest length for 'BLAKE2b' is 512 bits From 06b86928ab90aa017930310ff5e5815a2652013a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 28 Sep 2025 17:40:35 +0200 Subject: [PATCH 2/2] cksum: add support for sha2 --- src/uu/cksum/src/cksum.rs | 19 ++- src/uucore/src/lib/features/checksum.rs | 62 +++++++- tests/by-util/test_cksum.rs | 194 ++++++++++++++++++++++++ 3 files changed, 269 insertions(+), 6 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index fb3d8c23ed2..a0200c6c817 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -14,9 +14,9 @@ use std::iter; use std::path::Path; use uucore::checksum::{ ALGORITHM_OPTIONS_BLAKE2B, ALGORITHM_OPTIONS_BSD, ALGORITHM_OPTIONS_CRC, - ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_SYSV, ChecksumError, ChecksumOptions, - ChecksumVerbose, SUPPORTED_ALGORITHMS, detect_algo, digest_reader, perform_checksum_validation, - validate_blake2b_length_with_fluent, + ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_SHA2, ALGORITHM_OPTIONS_SYSV, ChecksumError, + ChecksumOptions, ChecksumVerbose, SUPPORTED_ALGORITHMS, detect_algo, digest_reader, + perform_checksum_validation, validate_blake2b_length, }; use uucore::translate; @@ -255,7 +255,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let length = match input_length { Some(length_str) => { if algo_name == ALGORITHM_OPTIONS_BLAKE2B { - validate_blake2b_length_with_fluent(length_str, "cksum")? + validate_blake2b_length(length_str, "cksum")? + } else if algo_name == ALGORITHM_OPTIONS_SHA2 + || algo_name.starts_with("sha3") + || algo_name == "shake128" + || algo_name == "shake256" + { + // Parse length for sha2, sha3, and shake algorithms + Some( + length_str + .parse::() + .map_err(|_| ChecksumError::InvalidLength)?, + ) } else { return Err(ChecksumError::LengthOnlyForBlake2b.into()); } diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 6638d613b14..a722c4f49d6 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -47,14 +47,16 @@ pub const ALGORITHM_OPTIONS_BLAKE3: &str = "blake3"; pub const ALGORITHM_OPTIONS_SM3: &str = "sm3"; pub const ALGORITHM_OPTIONS_SHAKE128: &str = "shake128"; pub const ALGORITHM_OPTIONS_SHAKE256: &str = "shake256"; +pub const ALGORITHM_OPTIONS_SHA2: &str = "sha2"; -pub const SUPPORTED_ALGORITHMS: [&str; 16] = [ +pub const SUPPORTED_ALGORITHMS: [&str; 17] = [ ALGORITHM_OPTIONS_SYSV, ALGORITHM_OPTIONS_BSD, ALGORITHM_OPTIONS_CRC, ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_MD5, ALGORITHM_OPTIONS_SHA1, + ALGORITHM_OPTIONS_SHA2, ALGORITHM_OPTIONS_SHA3, ALGORITHM_OPTIONS_SHA224, ALGORITHM_OPTIONS_SHA256, @@ -222,6 +224,10 @@ pub enum ChecksumError { BitsRequiredForShake128, #[error("--bits required for SHAKE256")] BitsRequiredForShake256, + #[error("--bits required for SHA2")] + BitsRequiredForSha2, + #[error("Invalid output size for SHA2 (expected 224, 256, 384, or 512), got {0}")] + InvalidSha2Length(usize), #[error("unknown algorithm: clap should have prevented this case")] UnknownAlgorithm, #[error("length is not a multiple of 8")] @@ -279,6 +285,42 @@ pub fn create_sha3(bits: usize) -> UResult { } } +/// Create a SHA-2 `HashAlgorithm` based on the specified output size in bits. +/// +/// # Arguments +/// +/// * `bits` - The output size in bits for the SHA-2 algorithm. +/// +/// # Returns +/// +/// Returns a `UResult` with an `HashAlgorithm` or an `Err` if an unsupported +/// output size is provided. +pub fn create_sha2(bits: usize) -> UResult { + match bits { + 224 => Ok(HashAlgorithm { + name: ALGORITHM_OPTIONS_SHA224, + create_fn: Box::new(|| Box::new(Sha224::new())), + bits: 224, + }), + 256 => Ok(HashAlgorithm { + name: ALGORITHM_OPTIONS_SHA256, + create_fn: Box::new(|| Box::new(Sha256::new())), + bits: 256, + }), + 384 => Ok(HashAlgorithm { + name: ALGORITHM_OPTIONS_SHA384, + create_fn: Box::new(|| Box::new(Sha384::new())), + bits: 384, + }), + 512 => Ok(HashAlgorithm { + name: ALGORITHM_OPTIONS_SHA512, + create_fn: Box::new(|| Box::new(Sha512::new())), + bits: 512, + }), + _ => Err(ChecksumError::InvalidSha2Length(bits).into()), + } +} + #[allow(clippy::comparison_chain)] fn print_cksum_report(res: &ChecksumResult) { if res.bad_format == 1 { @@ -458,11 +500,14 @@ pub fn detect_algo(algo: &str, length: Option) -> UResult bits, }) } + ALGORITHM_OPTIONS_SHA2 => { + let bits = length.ok_or(ChecksumError::BitsRequiredForSha2)?; + create_sha2(bits) + } _ if algo.starts_with("sha3") => { let bits = length.ok_or(ChecksumError::BitsRequiredForSha3)?; create_sha3(bits) } - _ => Err(ChecksumError::UnknownAlgorithm.into()), } } @@ -1434,6 +1479,18 @@ mod tests { detect_algo(ALGORITHM_OPTIONS_SHA512, None).unwrap().name, ALGORITHM_OPTIONS_SHA512 ); + assert_eq!( + detect_algo(ALGORITHM_OPTIONS_SHA2, Some(256)).unwrap().name, + ALGORITHM_OPTIONS_SHA256 + ); + assert_eq!( + detect_algo(ALGORITHM_OPTIONS_SHA2, Some(384)).unwrap().name, + ALGORITHM_OPTIONS_SHA384 + ); + assert_eq!( + detect_algo(ALGORITHM_OPTIONS_SHA2, Some(512)).unwrap().name, + ALGORITHM_OPTIONS_SHA512 + ); assert_eq!( detect_algo(ALGORITHM_OPTIONS_BLAKE2B, None).unwrap().name, ALGORITHM_OPTIONS_BLAKE2B @@ -1464,6 +1521,7 @@ mod tests { assert_eq!(detect_algo("sha3_512", Some(512)).unwrap().name, "SHA3_512"); assert!(detect_algo("sha3_512", None).is_err()); + assert!(detect_algo(ALGORITHM_OPTIONS_SHA2, None).is_err()); } #[test] diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 2819210d4cb..e12501d6f9d 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -2316,4 +2316,198 @@ mod format_mix { .stdout_contains("bar: OK") .stderr_contains("cksum: WARNING: 1 line is improperly formatted"); } + + #[test] + fn test_sha2_requires_length() { + // Test that sha2 algorithm requires a length parameter + new_ucmd!() + .arg("-a") + .arg("sha2") + .arg("lorem_ipsum.txt") + .fails_with_code(1) + .no_stdout() + .stderr_contains("--bits required for SHA2"); + } + + #[test] + fn test_sha2_with_length_224() { + // Test sha2 with 224-bit length (equivalent to sha224) + let result_sha2 = new_ucmd!() + .arg("-a") + .arg("sha2") + .arg("-l") + .arg("224") + .pipe_in("test\n") + .succeeds() + .stdout_str() + .to_owned(); + + let result_sha224 = new_ucmd!() + .arg("-a") + .arg("sha224") + .pipe_in("test\n") + .succeeds() + .stdout_str() + .to_owned(); + + // The outputs should be identical (both produce SHA224) + assert_eq!(result_sha2, result_sha224); + assert!(result_sha2.contains("SHA224")); + } + + #[test] + fn test_sha2_with_length_256() { + // Test sha2 with 256-bit length (equivalent to sha256) + let result_sha2 = new_ucmd!() + .arg("-a") + .arg("sha2") + .arg("-l") + .arg("256") + .pipe_in("test\n") + .succeeds() + .stdout_str() + .to_owned(); + + let result_sha256 = new_ucmd!() + .arg("-a") + .arg("sha256") + .pipe_in("test\n") + .succeeds() + .stdout_str() + .to_owned(); + + // The outputs should be identical (both produce SHA256) + assert_eq!(result_sha2, result_sha256); + assert!(result_sha2.contains("SHA256")); + } + + #[test] + fn test_sha2_with_length_384() { + // Test sha2 with 384-bit length (equivalent to sha384) + let result_sha2 = new_ucmd!() + .arg("-a") + .arg("sha2") + .arg("-l") + .arg("384") + .pipe_in("test\n") + .succeeds() + .stdout_str() + .to_owned(); + + let result_sha384 = new_ucmd!() + .arg("-a") + .arg("sha384") + .pipe_in("test\n") + .succeeds() + .stdout_str() + .to_owned(); + + // The outputs should be identical (both produce SHA384) + assert_eq!(result_sha2, result_sha384); + assert!(result_sha2.contains("SHA384")); + } + + #[test] + fn test_sha2_with_length_512() { + // Test sha2 with 512-bit length (equivalent to sha512) + let result_sha2 = new_ucmd!() + .arg("-a") + .arg("sha2") + .arg("-l") + .arg("512") + .pipe_in("test\n") + .succeeds() + .stdout_str() + .to_owned(); + + let result_sha512 = new_ucmd!() + .arg("-a") + .arg("sha512") + .pipe_in("test\n") + .succeeds() + .stdout_str() + .to_owned(); + + // The outputs should be identical (both produce SHA512) + assert_eq!(result_sha2, result_sha512); + assert!(result_sha2.contains("SHA512")); + } + + #[test] + fn test_sha2_invalid_length() { + // Test sha2 with invalid length parameters + new_ucmd!() + .arg("-a") + .arg("sha2") + .arg("-l") + .arg("128") + .pipe_in("test\n") + .fails_with_code(1) + .stderr_contains("Invalid output size for SHA2"); + + new_ucmd!() + .arg("-a") + .arg("sha2") + .arg("-l") + .arg("1024") + .pipe_in("test\n") + .fails_with_code(1) + .stderr_contains("Invalid output size for SHA2"); + } + + #[test] + fn test_sha2_with_raw_output() { + // Test sha2 with raw (binary) output + let result = new_ucmd!() + .arg("-a") + .arg("sha2") + .arg("-l") + .arg("256") + .arg("--raw") + .pipe_in("test\n") + .succeeds(); + + // Raw output should be binary, not hex - check raw bytes instead of trying to convert to string + let stdout_bytes = result.stdout(); + // Raw output should not be empty and should be exactly 32 bytes for SHA256 + assert_eq!(stdout_bytes.len(), 32); + // Should not contain ASCII text like "SHA256" + assert!(!stdout_bytes.starts_with(b"SHA256")); + } + + #[test] + fn test_sha2_with_base64_output() { + // Test sha2 with base64 output + let result = new_ucmd!() + .arg("-a") + .arg("sha2") + .arg("-l") + .arg("256") + .arg("--base64") + .pipe_in("test\n") + .succeeds() + .stdout_str() + .to_owned(); + + // Should contain base64 characters and SHA256 label + assert!(result.contains("SHA256")); + // Base64 should end with = or == or contain +/ + assert!(result.contains('=') || result.contains('+') || result.contains('/')); + } + + #[test] + fn test_sha2_with_file() { + // Test sha2 with actual file input + let (at, mut ucmd) = at_and_ucmd!(); + at.write("test_file.txt", "Hello, world!\n"); + + ucmd.arg("-a") + .arg("sha2") + .arg("-l") + .arg("256") + .arg("test_file.txt") + .succeeds() + .stdout_contains("SHA256") + .stdout_contains("test_file.txt"); + } }