Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 51 additions & 45 deletions src/uu/cksum/src/cksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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;

Expand Down Expand Up @@ -186,13 +186,14 @@ fn cksum<'a, I>(mut options: Options, files: I) -> UResult<()>
where
I: Iterator<Item = &'a OsStr>,
{
let files: Vec<_> = files.collect();
let mut files = files.peekable();

if options.output_format.is_raw() && files.len() > 1 {
return Err(Box::new(ChecksumError::RawMultipleFiles));
}
while let Some(filename) = files.next() {
// Check that in raw mode, we are not provided with several files.
if options.output_format.is_raw() && files.peek().is_some() {
return Err(Box::new(ChecksumError::RawMultipleFiles));
}

for filename in files {
let filepath = Path::new(filename);
let stdin_buf;
let file_buf;
Expand Down Expand Up @@ -363,42 +364,45 @@ 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)?;

let check = matches.get_flag(options::CHECK);

let algo_name: &str = match matches.get_one::<String>(options::ALGORITHM) {
Some(v) => v,
None => {
if check {
// if we are doing a --check, we should not default to crc
""
} else {
ALGORITHM_OPTIONS_CRC
}
}
};
let algo_cli = matches
.get_one::<String>(options::ALGORITHM)
.map(String::as_str);

let input_length = matches.get_one::<usize>(options::LENGTH);

let length = match (input_length, algo_name) {
// Length for sha2 and sha3 should be saved, it will be validated
// afterwards if necessary.
(Some(len), 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), ALGORITHM_OPTIONS_BLAKE2B) => calculate_blake2b_length(*len)?,
// a --length flag set with any other algorithm is an error.
_ => {
return Err(ChecksumError::LengthOnlyForBlake2bSha2Sha3.into());
}
};
let input_length = matches
.get_one::<String>(options::LENGTH)
.map(String::as_str);

if LEGACY_ALGORITHMS.contains(&algo_name) && check {
return Err(ChecksumError::AlgorithmNotSupportedWithCheck.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.
Expand All @@ -408,6 +412,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
);

if check {
// cksum does not support '--check'ing legacy algorithms
if algo_cli.is_some_and(|algo_name| LEGACY_ALGORITHMS.contains(&algo_name)) {
return Err(ChecksumError::AlgorithmNotSupportedWithCheck.into());
}

let text_flag = matches.get_flag(options::TEXT);
let binary_flag = matches.get_flag(options::BINARY);
let strict = matches.get_flag(options::STRICT);
Expand All @@ -421,13 +430,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
return Err(ChecksumError::BinaryTextConflict.into());
}

// Determine the appropriate algorithm option to pass
let algo_option = if algo_name.is_empty() {
None
} else {
Some(algo_name)
};

// Execute the checksum validation based on the presence of files or the use of stdin

let verbose = ChecksumVerbose::new(status, quiet, warn);
Expand All @@ -438,9 +440,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
verbose,
};

return perform_checksum_validation(files, algo_option, length, opts);
return perform_checksum_validation(files, algo_cli, length, opts);
}

// Not --check

// Set the default algorithm to CRC when not '--check'ing.
let algo_name = algo_cli.unwrap_or(ALGORITHM_OPTIONS_CRC);

let (tag, binary) = handle_tag_text_binary_flags(std::env::args_os())?;

let algo = detect_algo(algo_name, length)?;
Expand Down Expand Up @@ -508,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),
Expand Down
109 changes: 81 additions & 28 deletions src/uucore/src/lib/features/checksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::{
fmt::Display,
fs::File,
io::{self, BufReader, Read, Write, stdin},
num::IntErrorKind,
path::Path,
str,
};
Expand Down Expand Up @@ -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")]
Expand All @@ -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),
}
Expand Down Expand Up @@ -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()),
}
}

Expand All @@ -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()),
}
}

Expand Down Expand Up @@ -837,25 +840,41 @@ fn identify_algo_name_and_length(
last_algo: &mut Option<String>,
) -> Result<(String, Option<usize>), LineCheckError> {
let algo_from_line = line_info.algo_name.clone().unwrap_or_default();
let algorithm = algo_from_line.to_lowercase();
let line_algo = algo_from_line.to_lowercase();
*last_algo = Some(algo_from_line);

// check if we are called with XXXsum (example: md5sum) but we detected a different algo parsing the file
// (for example SHA1 (f) = d...)
// check if we are called with XXXsum (example: md5sum) but we detected a
// different algo parsing the file (for example SHA1 (f) = d...)
//
// Also handle the case cksum -s sm3 but the file contains other formats
if algo_name_input.is_some() && algo_name_input != Some(&algorithm) {
return Err(LineCheckError::ImproperlyFormatted);
if let Some(algo_name_input) = algo_name_input {
match (algo_name_input, line_algo.as_str()) {
(l, r) if l == r => (),
// Edge case for SHA2, which matches SHA(224|256|384|512)
(
ALGORITHM_OPTIONS_SHA2,
ALGORITHM_OPTIONS_SHA224
| ALGORITHM_OPTIONS_SHA256
| ALGORITHM_OPTIONS_SHA384
| ALGORITHM_OPTIONS_SHA512,
) => (),
_ => return Err(LineCheckError::ImproperlyFormatted),
}
}

if !SUPPORTED_ALGORITHMS.contains(&algorithm.as_str()) {
if !SUPPORTED_ALGORITHMS.contains(&line_algo.as_str()) {
// Not supported algo, leave early
return Err(LineCheckError::ImproperlyFormatted);
}

let bytes = if let Some(bitlen) = line_info.algo_bit_len {
match algorithm.as_str() {
match line_algo.as_str() {
ALGORITHM_OPTIONS_BLAKE2B if bitlen % 8 == 0 => Some(bitlen / 8),
ALGORITHM_OPTIONS_SHA3 if [224, 256, 384, 512].contains(&bitlen) => Some(bitlen),
ALGORITHM_OPTIONS_SHA2 | ALGORITHM_OPTIONS_SHA3
if [224, 256, 384, 512].contains(&bitlen) =>
{
Some(bitlen)
}
// Either
// the algo based line is provided with a bit length
// with an algorithm that does not support it (only Blake2B does).
Expand All @@ -866,14 +885,14 @@ fn identify_algo_name_and_length(
// the given length is wrong because it's not a multiple of 8.
_ => return Err(LineCheckError::ImproperlyFormatted),
}
} else if algorithm == ALGORITHM_OPTIONS_BLAKE2B {
} else if line_algo == ALGORITHM_OPTIONS_BLAKE2B {
// Default length with BLAKE2b,
Some(64)
} else {
None
};

Ok((algorithm, bytes))
Ok((line_algo, bytes))
}

/// Given a filename and an algorithm, compute the digest and compare it with
Expand Down Expand Up @@ -1219,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.
Expand All @@ -1243,17 +1270,43 @@ pub fn calculate_blake2b_length(length: usize) -> UResult<Option<usize>> {
Ok(Some(n / 8))
}
}
Err(_) => Err(ChecksumError::InvalidLength(length.into()).into()),
}
}

pub fn validate_sha2_sha3_length(algo_name: &str, length: Option<usize>) -> UResult<usize> {
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())
}
}

Expand Down
Loading
Loading