util(cksum): Fix gnu cksum-sha3.sh

This commit is contained in:
Dorian Peron 2025-10-28 03:10:56 +01:00
parent e41c842c4f
commit e57902f5df
2 changed files with 86 additions and 35 deletions

View file

@ -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};
@ -16,8 +16,8 @@ use uucore::checksum::{
ALGORITHM_OPTIONS_BLAKE2B, ALGORITHM_OPTIONS_BSD, ALGORITHM_OPTIONS_CRC,
ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_SHA2, ALGORITHM_OPTIONS_SHA3,
ALGORITHM_OPTIONS_SYSV, ChecksumError, ChecksumOptions, ChecksumVerbose, HashAlgorithm,
LEGACY_ALGORITHMS, SUPPORTED_ALGORITHMS, calculate_blake2b_length, detect_algo, digest_reader,
perform_checksum_validation,
LEGACY_ALGORITHMS, SUPPORTED_ALGORITHMS, calculate_blake2b_length_str, detect_algo,
digest_reader, perform_checksum_validation, sanitize_sha2_sha3_length_str,
};
use uucore::translate;
@ -364,6 +364,30 @@ fn figure_out_output_format(
}
}
/// Sanitize the `--length` argument depending on `--algorithm` and `--length`.
fn maybe_sanitize_length(
algo_cli: Option<&str>,
input_length: Option<&str>,
) -> UResult<Option<usize>> {
match (algo_cli, input_length) {
// No provided length is not a problem so far.
(_, None) => Ok(None),
// For SHA2 and SHA3, if a length is provided, ensure it is correct.
(Some(algo @ (ALGORITHM_OPTIONS_SHA2 | ALGORITHM_OPTIONS_SHA3)), Some(s_len)) => {
sanitize_sha2_sha3_length_str(algo, s_len).map(Some)
}
// For BLAKE2b, if a length is provided, validate it.
(Some(ALGORITHM_OPTIONS_BLAKE2B), Some(len)) => calculate_blake2b_length_str(len),
// For any other provided algorithm, check if length is 0.
// Otherwise, this is an error.
(_, Some(len)) if len.parse::<u32>() == Ok(0_u32) => Ok(None),
(_, Some(_)) => Err(ChecksumError::LengthOnlyForBlake2bSha2Sha3.into()),
}
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
@ -374,20 +398,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.get_one::<String>(options::ALGORITHM)
.map(String::as_str);
let input_length = matches.get_one::<usize>(options::LENGTH);
let input_length = matches
.get_one::<String>(options::LENGTH)
.map(String::as_str);
let length = match (input_length, algo_cli) {
// Length for sha2 and sha3 should be saved, it will be validated
// afterwards if necessary.
(Some(len), Some(ALGORITHM_OPTIONS_SHA2 | ALGORITHM_OPTIONS_SHA3)) => Some(*len),
(None | Some(0), _) => None,
// Length for Blake2b if saved only if it's not zero.
(Some(len), Some(ALGORITHM_OPTIONS_BLAKE2B)) => calculate_blake2b_length(*len)?,
// a --length flag set with any other algorithm is an error.
_ => {
return Err(ChecksumError::LengthOnlyForBlake2bSha2Sha3.into());
}
};
let length = maybe_sanitize_length(algo_cli, input_length)?;
let files = matches.get_many::<OsString>(options::FILE).map_or_else(
// No files given, read from stdin.
@ -500,7 +515,6 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::LENGTH)
.long(options::LENGTH)
.value_parser(value_parser!(usize))
.short('l')
.help(translate!("cksum-help-length"))
.action(ArgAction::Set),

View file

@ -12,6 +12,7 @@ use std::{
fmt::Display,
fs::File,
io::{self, BufReader, Read, Write, stdin},
num::IntErrorKind,
path::Path,
str,
};
@ -220,12 +221,12 @@ pub enum ChecksumError {
QuietNotCheck,
#[error("--length required for {}", .0.quote())]
LengthRequired(String),
#[error("unknown algorithm: {0}: clap should have prevented this case")]
UnknownAlgorithm(String),
#[error("length is not a multiple of 8")]
InvalidLength,
#[error("invalid length: {}", .0.quote())]
InvalidLength(String),
#[error("digest length for {} must be 224, 256, 384, or 512", .0.quote())]
InvalidLengthFor(String),
InvalidLengthForSha(String),
#[error("--algorithm={0} requires specifying --length 224, 256, 384, or 512")]
LengthRequiredForSha(String),
#[error("--length is only supported with --algorithm blake2b, sha2, or sha3")]
LengthOnlyForBlake2bSha2Sha3,
#[error("the --binary and --text options are meaningless when verifying checksums")]
@ -238,6 +239,8 @@ pub enum ChecksumError {
CombineMultipleAlgorithms,
#[error("Needs an algorithm to hash with.\nUse --help for more information.")]
NeedAlgorithmToHash,
#[error("unknown algorithm: {0}: clap should have prevented this case")]
UnknownAlgorithm(String),
#[error("")]
Io(#[from] io::Error),
}
@ -277,7 +280,7 @@ pub fn create_sha3(bits: usize) -> UResult<HashAlgorithm> {
bits: 512,
}),
_ => Err(ChecksumError::InvalidLengthFor("SHA3".into()).into()),
_ => Err(ChecksumError::InvalidLengthForSha("SHA3".into()).into()),
}
}
@ -304,7 +307,7 @@ pub fn create_sha2(bits: usize) -> UResult<HashAlgorithm> {
bits: 512,
}),
_ => Err(ChecksumError::InvalidLengthFor("SHA2".into()).into()),
_ => Err(ChecksumError::InvalidLengthForSha("SHA2".into()).into()),
}
}
@ -1235,21 +1238,29 @@ pub fn digest_reader<T: Read>(
/// Calculates the length of the digest.
pub fn calculate_blake2b_length(length: usize) -> UResult<Option<usize>> {
match length {
0 => Ok(None),
n if n % 8 != 0 => {
show_error!("invalid length: \u{2018}{length}\u{2019}");
calculate_blake2b_length_str(length.to_string().as_str())
}
/// Calculates the length of the digest.
pub fn calculate_blake2b_length_str(length: &str) -> UResult<Option<usize>> {
match length.parse() {
Ok(0) => Ok(None),
Ok(n) if n % 8 != 0 => {
show_error!("{}", ChecksumError::InvalidLength(length.into()));
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}");
Ok(n) if n > 512 => {
show_error!("{}", ChecksumError::InvalidLength(length.into()));
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"maximum digest length for \u{2018}BLAKE2b\u{2019} is 512 bits",
format!(
"maximum digest length for {} is 512 bits",
"BLAKE2b".quote()
),
)
.into())
}
n => {
Ok(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.
@ -1259,6 +1270,7 @@ pub fn calculate_blake2b_length(length: usize) -> UResult<Option<usize>> {
Ok(Some(n / 8))
}
}
Err(_) => Err(ChecksumError::InvalidLength(length.into()).into()),
}
}
@ -1266,10 +1278,35 @@ pub fn validate_sha2_sha3_length(algo_name: &str, length: Option<usize>) -> URes
match length {
Some(len @ (224 | 256 | 384 | 512)) => Ok(len),
Some(len) => {
show_error!("invalid length: '{len}'");
Err(ChecksumError::InvalidLengthFor(algo_name.to_ascii_uppercase()).into())
show_error!("{}", ChecksumError::InvalidLength(len.to_string()));
Err(ChecksumError::InvalidLengthForSha(algo_name.to_ascii_uppercase()).into())
}
None => Err(ChecksumError::LengthRequired(algo_name.to_ascii_uppercase()).into()),
None => Err(ChecksumError::LengthRequiredForSha(algo_name.into()).into()),
}
}
pub fn sanitize_sha2_sha3_length_str(algo_name: &str, length: &str) -> UResult<usize> {
// There is a difference in the errors sent when the length is not a number
// vs. its an invalid number.
//
// When inputting an invalid number, an extra error message it printed to
// remind of the accepted inputs.
let len = match length.parse::<usize>() {
Ok(l) => l,
// Note: Positive overflow while parsing counts as an invalid number,
// but a number still.
Err(e) if *e.kind() == IntErrorKind::PosOverflow => {
show_error!("{}", ChecksumError::InvalidLength(length.into()));
return Err(ChecksumError::InvalidLengthForSha(algo_name.to_ascii_uppercase()).into());
}
Err(_) => return Err(ChecksumError::InvalidLength(length.into()).into()),
};
if [224, 256, 384, 512].contains(&len) {
Ok(len)
} else {
show_error!("{}", ChecksumError::InvalidLength(length.into()));
Err(ChecksumError::InvalidLengthForSha(algo_name.to_ascii_uppercase()).into())
}
}