From 7e33650c8c7f4e8ea852c341ebd634b8121a22cf Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 May 2024 22:33:10 +0200 Subject: [PATCH 01/15] cksum: move the length mgmt into a function + add tests --- src/uu/cksum/src/cksum.rs | 94 +++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index a50abada5..25080040f 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -353,6 +353,41 @@ fn had_reset(args: &[String]) -> bool { } } +/// Calculates the length of the digest for the given algorithm. +fn calculate_length(algo_name: &str, length: usize) -> UResult> { + match length { + 0 => Ok(None), + n if n % 8 != 0 => { + uucore::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 => { + uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "maximum digest length for \u{2018}BLAKE2b\u{2019} is 512 bits", + ) + .into()) + }, + n => { + if algo_name == ALGORITHM_OPTIONS_BLAKE2B { + // Divide by 8, as our blake2b implementation expects bytes instead of bits. + Ok(Some(n / 8)) + } else { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "--length is only supported with --algorithm=blake2b", + ) + .into()) + } + } + } +} + /*** * cksum has a bunch of legacy behavior. * We handle this in this function to make sure they are self contained @@ -391,46 +426,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let input_length = matches.get_one::(options::LENGTH); - let check = matches.get_flag(options::CHECK); - let length = if let Some(length) = input_length { - match length.to_owned() { - 0 => None, - n if n % 8 != 0 => { - // GNU's implementation seem to use these quotation marks - // in their error messages, so we do the same. - uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "length is not a multiple of 8", - ) - .into()); - } - n if n > 512 => { - uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); - - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "maximum digest length for \u{2018}BLAKE2b\u{2019} is 512 bits", - ) - .into()); - } - n => { - if algo_name != ALGORITHM_OPTIONS_BLAKE2B { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "--length is only supported with --algorithm=blake2b", - ) - .into()); - } - - // Divide by 8, as our blake2b implementation expects bytes - // instead of bits. - Some(n / 8) - } - } - } else { - None + let length = match input_length { + Some(length) => calculate_length(algo_name, *length)?, + None => None, }; let (tag, asterisk) = handle_tag_text_binary_flags(&matches, check)?; @@ -581,6 +580,7 @@ pub fn uu_app() -> Command { mod tests { use super::had_reset; use crate::prompt_asterisk; + use crate::calculate_length; #[test] fn test_had_reset() { @@ -646,4 +646,20 @@ mod tests { assert!(prompt_asterisk(false, true, false)); assert!(!prompt_asterisk(false, false, false)); } + + #[test] + fn test_calculate_length() { + assert_eq!( + calculate_length(crate::ALGORITHM_OPTIONS_BLAKE2B, 256).unwrap(), + Some(32) + ); + + calculate_length(crate::ALGORITHM_OPTIONS_BLAKE2B, 255).unwrap_err(); + + calculate_length(crate::ALGORITHM_OPTIONS_SHA256, 33).unwrap_err(); + + calculate_length(crate::ALGORITHM_OPTIONS_BLAKE2B, 513).unwrap_err(); + + calculate_length(crate::ALGORITHM_OPTIONS_SHA256, 256).unwrap_err(); + } } From 5f0b48e649360e5473d5df5085325e9699326138 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 9 May 2024 08:54:10 +0200 Subject: [PATCH 02/15] cksum: use the qualified import to make its provenance clear --- src/uu/cksum/src/cksum.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 25080040f..2397cca78 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -5,8 +5,6 @@ // spell-checker:ignore (ToDO) fname, algo use clap::{crate_version, value_parser, Arg, ArgAction, Command}; -use hex::decode; -use hex::encode; use std::error::Error; use std::ffi::OsStr; use std::fmt::Display; @@ -205,7 +203,7 @@ where ALGORITHM_OPTIONS_SYSV | ALGORITHM_OPTIONS_BSD => { sum_hex.parse::().unwrap().to_be_bytes().to_vec() } - _ => decode(sum_hex).unwrap(), + _ => hex::decode(sum_hex).unwrap(), }; // Cannot handle multiple files anyway, output immediately. stdout().write_all(&bytes)?; @@ -214,7 +212,7 @@ where 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(), + _ => encoding::encode(encoding::Format::Base64, &hex::decode(sum_hex).unwrap()).unwrap(), }, }; // The BSD checksum output is 5 digit integer @@ -299,7 +297,7 @@ fn digest_read( // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) let mut bytes = vec![0; (output_bits + 7) / 8]; digest.hash_finalize(&mut bytes); - Ok((encode(bytes), output_size)) + Ok((hex::encode(bytes), output_size)) } } From 81500ae3b8cadf407ce1f1d42fd65bb15e1b1abb Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 9 May 2024 10:10:47 +0200 Subject: [PATCH 03/15] cksum: implement check & strict --- Cargo.lock | 1 + src/uu/cksum/Cargo.toml | 1 + src/uu/cksum/src/cksum.rs | 208 +++++++++++++++++++++++++++++++----- tests/by-util/test_cksum.rs | 143 +++++++++++++++++++++++++ 4 files changed, 326 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d229aed4..a1951a025 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2494,6 +2494,7 @@ version = "0.0.26" dependencies = [ "clap", "hex", + "regex", "uucore", ] diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index f6a5a138a..dcd5b56b1 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -18,6 +18,7 @@ path = "src/cksum.rs" clap = { workspace = true } uucore = { workspace = true, features = ["encoding", "sum"] } hex = { workspace = true } +regex = { workspace = true } [[bin]] name = "cksum" diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 2397cca78..746b22b10 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -5,13 +5,18 @@ // spell-checker:ignore (ToDO) fname, algo use clap::{crate_version, value_parser, Arg, ArgAction, Command}; +use regex::Regex; +use std::cmp::Ordering; use std::error::Error; use std::ffi::OsStr; use std::fmt::Display; use std::fs::File; +use std::io::BufRead; use std::io::{self, stdin, stdout, BufReader, Read, Write}; use std::iter; use std::path::Path; +use uucore::error::set_exit_code; +use uucore::show_warning_caps; use uucore::{ encoding, error::{FromIo, UError, UResult, USimpleError}, @@ -212,7 +217,8 @@ where 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, &hex::decode(sum_hex).unwrap()).unwrap(), + _ => encoding::encode(encoding::Format::Base64, &hex::decode(sum_hex).unwrap()) + .unwrap(), }, }; // The BSD checksum output is 5 digit integer @@ -310,6 +316,7 @@ mod options { pub const RAW: &str = "raw"; pub const BASE64: &str = "base64"; pub const CHECK: &str = "check"; + pub const STRICT: &str = "strict"; pub const TEXT: &str = "text"; pub const BINARY: &str = "binary"; } @@ -357,12 +364,8 @@ fn calculate_length(algo_name: &str, length: usize) -> UResult> { 0 => Ok(None), n if n % 8 != 0 => { uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); - Err(io::Error::new( - io::ErrorKind::InvalidInput, - "length is not a multiple of 8", - ) - .into()) - }, + Err(io::Error::new(io::ErrorKind::InvalidInput, "length is not a multiple of 8").into()) + } n if n > 512 => { uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); Err(io::Error::new( @@ -370,7 +373,7 @@ fn calculate_length(algo_name: &str, length: usize) -> UResult> { "maximum digest length for \u{2018}BLAKE2b\u{2019} is 512 bits", ) .into()) - }, + } n => { if algo_name == ALGORITHM_OPTIONS_BLAKE2B { // Divide by 8, as our blake2b implementation expects bytes instead of bits. @@ -391,12 +394,11 @@ fn calculate_length(algo_name: &str, length: usize) -> UResult> { * We handle this in this function to make sure they are self contained * and "easier" to understand */ -fn handle_tag_text_binary_flags(matches: &clap::ArgMatches, check: bool) -> UResult<(bool, bool)> { +fn handle_tag_text_binary_flags(matches: &clap::ArgMatches) -> UResult<(bool, bool)> { let untagged: bool = matches.get_flag(options::UNTAGGED); let tag: bool = matches.get_flag(options::TAG); let tag: bool = tag || !untagged; - let text_flag: bool = matches.get_flag(options::TEXT); let binary_flag: bool = matches.get_flag(options::BINARY); let args: Vec = std::env::args().collect(); @@ -404,34 +406,158 @@ fn handle_tag_text_binary_flags(matches: &clap::ArgMatches, check: bool) -> URes let asterisk: bool = prompt_asterisk(tag, binary_flag, had_reset); - if (binary_flag || text_flag) && check { + Ok((tag, asterisk)) +} + +/*** + * Do the checksum validation (can be strict or not) +*/ +fn perform_checksum_validation<'a, I>(files: I, strict: bool) -> UResult<()> +where + I: Iterator, +{ + let re = Regex::new(r"(?P\w+)(-(?P\d+))? \((?P.*)\) = (?P.*)") + .unwrap(); + let mut properly_formatted = false; + let mut bad_format = 0; + let mut failed_cksum = 0; + let mut failed_open_file = 0; + for filename_input in files { + let input_is_stdin = filename_input == OsStr::new("-"); + let file: Box = if input_is_stdin { + Box::new(stdin()) // Use stdin if "-" is specified + } else { + match File::open(filename_input) { + Ok(f) => Box::new(f), + Err(err) => { + show!(err.map_err_context(|| format!( + "Failed to open file: {}", + filename_input.to_string_lossy() + ))); + return Err(io::Error::new(io::ErrorKind::Other, "Failed to open file").into()); + } + } + }; + let reader = BufReader::new(file); + + for line in reader.lines() { + let line = line?; + + if let Some(caps) = re.captures(&line) { + properly_formatted = true; + let algo_name = caps.name("algo").unwrap().as_str().to_lowercase(); + let filename_to_check = caps.name("filename").unwrap().as_str(); + let expected_checksum = caps.name("checksum").unwrap().as_str(); + + let length = caps + .name("bits") + .map(|m| m.as_str().parse::().unwrap() / 8); + let (_, mut algo, bits) = detect_algo(&algo_name, length); + + let file_to_check: Box = if filename_to_check == "-" { + Box::new(stdin()) // Use stdin if "-" is specified in the checksum file + } else { + match File::open(filename_to_check) { + Ok(f) => Box::new(f), + Err(err) => { + show!(err.map_err_context(|| format!( + "Failed to open file: {}", + filename_to_check + ))); + failed_open_file += 1; + // we could not open the file but we want to continue + continue; + } + } + }; + let mut file_reader = BufReader::new(file_to_check); + + let (calculated_checksum, _) = digest_read(&mut algo, &mut file_reader, bits) + .map_err_context(|| "failed to read input".to_string())?; + if expected_checksum == calculated_checksum { + println!("{}: OK", filename_to_check); + } else { + println!("{}: FAILED", filename_to_check); + failed_cksum += 1; + } + } else { + bad_format += 1; + } + } + } + + // not a single line correctly formatted found + // return an error + if !properly_formatted { return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "the --binary and --text options are meaningless when verifying checksums", + io::ErrorKind::Other, + "no properly formatted checksum lines found", ) .into()); } - Ok((tag, asterisk)) + + // strict means that we should have an exit code. + if strict && bad_format > 0 { + set_exit_code(1); + } + + // if any incorrectly formatted line, show it + match bad_format.cmp(&1) { + Ordering::Equal => { + show_warning_caps!("{} line is improperly formatted", bad_format); + } + Ordering::Greater => { + show_warning_caps!("{} lines are improperly formatted", bad_format); + } + Ordering::Less => {} + }; + + // if we have any failed checksum verification, we set an exit code + if failed_cksum > 0 || failed_open_file > 0 { + set_exit_code(1); + } + + match failed_open_file.cmp(&1) { + Ordering::Equal => { + show_warning_caps!("{} listed file could not be read", failed_open_file); + } + Ordering::Greater => { + show_warning_caps!("{} listed files could not be read", failed_open_file); + } + Ordering::Less => {} + } + + match failed_cksum.cmp(&1) { + Ordering::Equal => { + show_warning_caps!("{} computed checksum did NOT match", failed_cksum); + } + Ordering::Greater => { + show_warning_caps!("{} computed checksums did NOT match", failed_cksum); + } + Ordering::Less => {} + }; + + Ok(()) } #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; + let check = matches.get_flag(options::CHECK); + let algo_name: &str = match matches.get_one::(options::ALGORITHM) { Some(v) => v, - None => ALGORITHM_OPTIONS_CRC, + None => { + if check { + // if we are doing a --check, we should not default to crc + "" + } else { + ALGORITHM_OPTIONS_CRC + } + } }; - let input_length = matches.get_one::(options::LENGTH); - - let length = match input_length { - Some(length) => calculate_length(algo_name, *length)?, - None => None, - }; - - let (tag, asterisk) = handle_tag_text_binary_flags(&matches, check)?; - if ["bsd", "crc", "sysv"].contains(&algo_name) && check { return Err(io::Error::new( io::ErrorKind::InvalidInput, @@ -440,6 +566,34 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .into()); } + if check { + let text_flag: bool = matches.get_flag(options::TEXT); + let binary_flag: bool = matches.get_flag(options::BINARY); + let strict = matches.get_flag(options::STRICT); + + if (binary_flag || text_flag) && check { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "the --binary and --text options are meaningless when verifying checksums", + ) + .into()); + } + + return match matches.get_many::(options::FILE) { + Some(files) => perform_checksum_validation(files.map(OsStr::new), strict), + None => perform_checksum_validation(iter::once(OsStr::new("-")), strict), + }; + } + + let input_length = matches.get_one::(options::LENGTH); + + let length = match input_length { + Some(length) => calculate_length(algo_name, *length)?, + None => None, + }; + + let (tag, asterisk) = handle_tag_text_binary_flags(&matches)?; + let (name, algo, bits) = detect_algo(algo_name, length); let output_format = if matches.get_flag(options::RAW) { @@ -532,12 +686,12 @@ pub fn uu_app() -> Command { .help("emit a raw binary digest, not hexadecimal") .action(ArgAction::SetTrue), ) - /*.arg( + .arg( Arg::new(options::STRICT) .long(options::STRICT) .help("exit non-zero for improperly formatted checksum lines") .action(ArgAction::SetTrue), - )*/ + ) .arg( Arg::new(options::CHECK) .short('c') @@ -577,8 +731,8 @@ pub fn uu_app() -> Command { #[cfg(test)] mod tests { use super::had_reset; - use crate::prompt_asterisk; use crate::calculate_length; + use crate::prompt_asterisk; #[test] fn test_had_reset() { diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index bed35c95e..7587fc51f 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -663,3 +663,146 @@ fn test_conflicting_options() { ) .code_is(1); } + +#[test] +fn test_check_algo_err() { + let scene = TestScenario::new(util_name!()); + + let at = &scene.fixtures; + + at.touch("f"); + + scene + .ucmd() + .arg("--a") + .arg("sm3") + .arg("--check") + .arg("f") + .fails() + .no_stdout() + .stderr_contains("cksum: no properly formatted checksum lines found") + .code_is(1); +} + +#[test] +fn test_cksum_check() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let commands = [ + vec!["-a", "sha384"], + vec!["-a", "blake2b"], + vec!["-a", "blake2b", "-l", "384"], + vec!["-a", "sm3"], + ]; + at.touch("f"); + at.touch("CHECKSUM"); + for command in &commands { + let result = scene.ucmd().args(command).arg("f").succeeds(); + at.append("CHECKSUM", result.stdout_str()); + } + scene + .ucmd() + .arg("--check") + .arg("CHECKSUM") + .succeeds() + .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n"); + scene + .ucmd() + .arg("--check") + .arg("--strict") + .arg("CHECKSUM") + .succeeds() + .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n"); + // inject invalid content + at.append("CHECKSUM", "incorrect data"); + scene + .ucmd() + .arg("--check") + .arg("CHECKSUM") + .succeeds() + .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n") + .stderr_contains("line is improperly formatted"); + scene + .ucmd() + .arg("--check") + .arg("--strict") + .arg("CHECKSUM") + .fails() + .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n") + .stderr_contains("line is improperly formatted"); +} + +#[test] +fn test_cksum_check_invalid() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let commands = [vec!["-a", "sha384"]]; + at.touch("f"); + at.touch("CHECKSUM"); + for command in &commands { + let result = scene.ucmd().args(command).arg("f").succeeds(); + at.append("CHECKSUM", result.stdout_str()); + } + // inject invalid content + at.append("CHECKSUM", "again incorrect data\naze\n"); + scene + .ucmd() + .arg("--check") + .arg("--strict") + .arg("CHECKSUM") + .fails() + .stdout_contains("f: OK\n") + .stderr_contains("2 lines"); + + // without strict, it passes + scene + .ucmd() + .arg("--check") + .arg("CHECKSUM") + .succeeds() + .stdout_contains("f: OK\n") + .stderr_contains("2 lines"); +} + +#[test] +fn test_cksum_check_failed() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let commands = [vec!["-a", "sha384"]]; + at.touch("f"); + at.touch("CHECKSUM"); + for command in &commands { + let result = scene.ucmd().args(command).arg("f").succeeds(); + at.append("CHECKSUM", result.stdout_str()); + } + // inject invalid content + at.append("CHECKSUM", "again incorrect data\naze\nSM3 (input) = 7cfb120d4fabea2a904948538a438fdb57c725157cb40b5aee8d937b8351477e\n"); + + let result = scene + .ucmd() + .arg("--check") + .arg("--strict") + .arg("CHECKSUM") + .fails(); + + assert!(result.stderr_str().contains("Failed to open file: input")); + assert!(result + .stderr_str() + .contains("2 lines are improperly formatted\n")); + assert!(result + .stderr_str() + .contains("1 listed file could not be read\n")); + assert!(result.stdout_str().contains("f: OK\n")); + + // without strict + let result = scene.ucmd().arg("--check").arg("CHECKSUM").fails(); + + assert!(result.stderr_str().contains("Failed to open file: input")); + assert!(result + .stderr_str() + .contains("2 lines are improperly formatted\n")); + assert!(result + .stderr_str() + .contains("1 listed file could not be read\n")); + assert!(result.stdout_str().contains("f: OK\n")); +} From 0b04bcaf9a977d7772b6aa1027c9b0f5dc8270f0 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 10 May 2024 23:49:02 +0200 Subject: [PATCH 04/15] uucore: create a new function to manage the warning/error display --- src/uu/cksum/Cargo.toml | 2 +- src/uu/cksum/src/cksum.rs | 35 +++---------------------- src/uu/hashsum/Cargo.toml | 2 +- src/uu/hashsum/src/hashsum.rs | 34 +++--------------------- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features.rs | 2 ++ src/uucore/src/lib/features/checksum.rs | 27 +++++++++++++++++++ src/uucore/src/lib/lib.rs | 2 ++ 8 files changed, 40 insertions(+), 65 deletions(-) create mode 100644 src/uucore/src/lib/features/checksum.rs diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index dcd5b56b1..208c01901 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -16,7 +16,7 @@ path = "src/cksum.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["encoding", "sum"] } +uucore = { workspace = true, features = ["checksum", "encoding", "sum"] } hex = { workspace = true } regex = { workspace = true } diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 746b22b10..7c66de2a1 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -6,7 +6,6 @@ // spell-checker:ignore (ToDO) fname, algo use clap::{crate_version, value_parser, Arg, ArgAction, Command}; use regex::Regex; -use std::cmp::Ordering; use std::error::Error; use std::ffi::OsStr; use std::fmt::Display; @@ -15,8 +14,8 @@ use std::io::BufRead; use std::io::{self, stdin, stdout, BufReader, Read, Write}; use std::iter; use std::path::Path; +use uucore::checksum::cksum_output; use uucore::error::set_exit_code; -use uucore::show_warning_caps; use uucore::{ encoding, error::{FromIo, UError, UResult, USimpleError}, @@ -501,41 +500,13 @@ where set_exit_code(1); } - // if any incorrectly formatted line, show it - match bad_format.cmp(&1) { - Ordering::Equal => { - show_warning_caps!("{} line is improperly formatted", bad_format); - } - Ordering::Greater => { - show_warning_caps!("{} lines are improperly formatted", bad_format); - } - Ordering::Less => {} - }; - // if we have any failed checksum verification, we set an exit code if failed_cksum > 0 || failed_open_file > 0 { set_exit_code(1); } - match failed_open_file.cmp(&1) { - Ordering::Equal => { - show_warning_caps!("{} listed file could not be read", failed_open_file); - } - Ordering::Greater => { - show_warning_caps!("{} listed files could not be read", failed_open_file); - } - Ordering::Less => {} - } - - match failed_cksum.cmp(&1) { - Ordering::Equal => { - show_warning_caps!("{} computed checksum did NOT match", failed_cksum); - } - Ordering::Greater => { - show_warning_caps!("{} computed checksums did NOT match", failed_cksum); - } - Ordering::Less => {} - }; + // if any incorrectly formatted line, show it + cksum_output(bad_format, failed_cksum, failed_open_file); Ok(()) } diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index f3799aedb..d450e3cef 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -16,7 +16,7 @@ path = "src/hashsum.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["sum"] } +uucore = { workspace = true, features = ["checksum", "sum"] } memchr = { workspace = true } regex = { workspace = true } hex = { workspace = true } diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 39b5a0a07..bddb4d5aa 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -12,7 +12,6 @@ use clap::{Arg, ArgMatches, Command}; use hex::encode; use regex::Captures; use regex::Regex; -use std::cmp::Ordering; use std::error::Error; use std::ffi::{OsStr, OsString}; use std::fs::File; @@ -20,6 +19,8 @@ use std::io::{self, stdin, BufRead, BufReader, Read}; use std::iter; use std::num::ParseIntError; use std::path::Path; +use uucore::checksum::cksum_output; +use uucore::display::Quotable; use uucore::error::USimpleError; use uucore::error::{set_exit_code, FromIo, UError, UResult}; use uucore::sum::{ @@ -27,7 +28,6 @@ use uucore::sum::{ Sha3_384, Sha3_512, Sha512, Shake128, Shake256, }; use uucore::util_name; -use uucore::{display::Quotable, show_warning_caps}; use uucore::{format_usage, help_about, help_usage}; const NAME: &str = "hashsum"; @@ -830,35 +830,7 @@ where } if !options.status && !skip_summary { - match bad_format.cmp(&1) { - Ordering::Equal => { - show_warning_caps!("{} line is improperly formatted", bad_format); - } - Ordering::Greater => { - show_warning_caps!("{} lines are improperly formatted", bad_format); - } - Ordering::Less => {} - }; - - match failed_cksum.cmp(&1) { - Ordering::Equal => { - show_warning_caps!("{} computed checksum did NOT match", failed_cksum); - } - Ordering::Greater => { - show_warning_caps!("{} computed checksums did NOT match", failed_cksum); - } - Ordering::Less => {} - }; - - match failed_open_file.cmp(&1) { - Ordering::Equal => { - show_warning_caps!("{} listed file could not be read", failed_open_file); - } - Ordering::Greater => { - show_warning_caps!("{} listed files could not be read", failed_open_file); - } - Ordering::Less => {} - } + cksum_output(bad_format, failed_cksum, failed_open_file); } Ok(()) diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 2e7140b6b..81d202398 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -75,6 +75,7 @@ default = [] # * non-default features backup-control = [] colors = [] +checksum = [] encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"] entries = ["libc"] fs = ["dunce", "libc", "winapi-util", "windows-sys"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 423ff34ba..ef7b17b31 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -6,6 +6,8 @@ #[cfg(feature = "backup-control")] pub mod backup_control; +#[cfg(feature = "checksum")] +pub mod checksum; #[cfg(feature = "colors")] pub mod colors; #[cfg(feature = "encoding")] diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs new file mode 100644 index 000000000..1652ce47c --- /dev/null +++ b/src/uucore/src/lib/features/checksum.rs @@ -0,0 +1,27 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use crate::show_warning_caps; + +#[allow(clippy::comparison_chain)] +pub fn cksum_output(bad_format: i32, failed_cksum: i32, failed_open_file: i32) { + if bad_format == 1 { + show_warning_caps!("{} line is improperly formatted", bad_format); + } else if bad_format > 1 { + show_warning_caps!("{} lines are improperly formatted", bad_format); + } + + if failed_cksum == 1 { + show_warning_caps!("{} computed checksum did NOT match", failed_cksum); + } else if failed_cksum > 1 { + show_warning_caps!("{} computed checksums did NOT match", failed_cksum); + } + + if failed_open_file == 1 { + show_warning_caps!("{} listed file could not be read", failed_open_file); + } else if failed_open_file > 1 { + show_warning_caps!("{} listed files could not be read", failed_open_file); + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index e891cc404..366c420d9 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -37,6 +37,8 @@ pub use crate::parser::shortcut_value_parser; // * feature-gated modules #[cfg(feature = "backup-control")] pub use crate::features::backup_control; +#[cfg(feature = "checksum")] +pub use crate::features::checksum; #[cfg(feature = "colors")] pub use crate::features::colors; #[cfg(feature = "encoding")] From 1aec8b407d8e40db6c6eea82747ed6ec50528820 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 11 May 2024 08:25:45 +0200 Subject: [PATCH 05/15] chksum: add support of --check --algorithm=xxx --- src/uu/cksum/src/cksum.rs | 162 ++++++++++++++++++++++++------------ tests/by-util/test_cksum.rs | 148 +++++++++++++++++++++++++++++++- 2 files changed, 254 insertions(+), 56 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 7c66de2a1..9f6ed3bec 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -75,10 +75,10 @@ impl Display for CkSumError { } fn detect_algo( - program: &str, + algo: &str, length: Option, ) -> (&'static str, Box, usize) { - match program { + match algo { ALGORITHM_OPTIONS_SYSV => ( ALGORITHM_OPTIONS_SYSV, Box::new(SYSV::new()) as Box, @@ -411,48 +411,99 @@ fn handle_tag_text_binary_flags(matches: &clap::ArgMatches) -> UResult<(bool, bo /*** * Do the checksum validation (can be strict or not) */ -fn perform_checksum_validation<'a, I>(files: I, strict: bool) -> UResult<()> +fn perform_checksum_validation<'a, I>( + files: I, + strict: bool, + algo_name_input: Option<&str>, +) -> UResult<()> where I: Iterator, { - let re = Regex::new(r"(?P\w+)(-(?P\d+))? \((?P.*)\) = (?P.*)") - .unwrap(); - let mut properly_formatted = false; - let mut bad_format = 0; - let mut failed_cksum = 0; - let mut failed_open_file = 0; + // Regexp to handle the two input formats: + // 1. [-] () = + // 2. [* ] + let regex_pattern = r"^(?P\w+)(-(?P\d+))?\s?\((?P.*)\) = (?P[a-fA-F0-9]+)$|^(?P[a-fA-F0-9]+)\s[* ](?P.*)"; + let re = Regex::new(regex_pattern).unwrap(); + + // if cksum has several input files, it will print the result for each file for filename_input in files { + let mut bad_format = 0; + let mut failed_cksum = 0; + let mut failed_open_file = 0; + let mut properly_formatted = false; let input_is_stdin = filename_input == OsStr::new("-"); + let file: Box = if input_is_stdin { Box::new(stdin()) // Use stdin if "-" is specified } else { match File::open(filename_input) { Ok(f) => Box::new(f), - Err(err) => { - show!(err.map_err_context(|| format!( - "Failed to open file: {}", - filename_input.to_string_lossy() - ))); - return Err(io::Error::new(io::ErrorKind::Other, "Failed to open file").into()); + Err(_) => { + return Err(io::Error::new( + io::ErrorKind::Other, + format!( + "{}: No such file or directory", + filename_input.to_string_lossy() + ), + ) + .into()); } } }; let reader = BufReader::new(file); + // for each line in the input, check if it is a valid checksum line for line in reader.lines() { - let line = line?; - + let line = line.unwrap_or_else(|_| String::new()); if let Some(caps) = re.captures(&line) { properly_formatted = true; - let algo_name = caps.name("algo").unwrap().as_str().to_lowercase(); - let filename_to_check = caps.name("filename").unwrap().as_str(); - let expected_checksum = caps.name("checksum").unwrap().as_str(); - let length = caps - .name("bits") - .map(|m| m.as_str().parse::().unwrap() / 8); - let (_, mut algo, bits) = detect_algo(&algo_name, length); + // Determine what kind of file input we had + // we need it for case "--check -a sm3 " when is + // [-] () = + let algo_based_format = + caps.name("filename1").is_some() && caps.name("checksum1").is_some(); + let filename_to_check = caps + .name("filename1") + .or(caps.name("filename2")) + .unwrap() + .as_str(); + let expected_checksum = caps + .name("checksum1") + .or(caps.name("checksum2")) + .unwrap() + .as_str(); + + // If the algo_name is provided, we use it, otherwise we try to detect it + let algo_details = if algo_based_format { + // When the algo-based format is matched, extract details from regex captures + let algorithm = caps.name("algo").map_or("", |m| m.as_str()).to_lowercase(); + let bits = caps + .name("bits") + .map(|m| m.as_str().parse::().unwrap() / 8); + (algorithm, bits) + } else if let Some(a) = algo_name_input { + // When a specific algorithm name is input, use it and default bits to None + (a.to_lowercase(), None) + } else { + // Default case if no algorithm is specified and non-algo based format is matched + (String::new(), None) + }; + if algo_based_format + && algo_name_input.map_or(false, |input| algo_details.0 != input) + { + bad_format += 1; + continue; + } + if algo_details.0.is_empty() { + // we haven't been able to detect the algo name. No point to continue + properly_formatted = false; + continue; + } + let (_, mut algo, bits) = detect_algo(&algo_details.0, algo_details.1); + + // manage the input file let file_to_check: Box = if filename_to_check == "-" { Box::new(stdin()) // Use stdin if "-" is specified in the checksum file } else { @@ -470,9 +521,11 @@ where } }; let mut file_reader = BufReader::new(file_to_check); + // Read the file and calculate the checksum + let (calculated_checksum, _) = + digest_read(&mut algo, &mut file_reader, bits).unwrap(); - let (calculated_checksum, _) = digest_read(&mut algo, &mut file_reader, bits) - .map_err_context(|| "failed to read input".to_string())?; + // Do the checksum validation if expected_checksum == calculated_checksum { println!("{}: OK", filename_to_check); } else { @@ -483,31 +536,29 @@ where bad_format += 1; } } + + // not a single line correctly formatted found + // return an error + if !properly_formatted { + uucore::show_error!( + "{}: no properly formatted checksum lines found", + filename_input.to_string_lossy() + ); + set_exit_code(1); + } + // strict means that we should have an exit code. + if strict && bad_format > 0 { + set_exit_code(1); + } + + // if we have any failed checksum verification, we set an exit code + if failed_cksum > 0 || failed_open_file > 0 { + set_exit_code(1); + } + + // if any incorrectly formatted line, show it + cksum_output(bad_format, failed_cksum, failed_open_file); } - - // not a single line correctly formatted found - // return an error - if !properly_formatted { - return Err(io::Error::new( - io::ErrorKind::Other, - "no properly formatted checksum lines found", - ) - .into()); - } - - // strict means that we should have an exit code. - if strict && bad_format > 0 { - set_exit_code(1); - } - - // if we have any failed checksum verification, we set an exit code - if failed_cksum > 0 || failed_open_file > 0 { - set_exit_code(1); - } - - // if any incorrectly formatted line, show it - cksum_output(bad_format, failed_cksum, failed_open_file); - Ok(()) } @@ -549,10 +600,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ) .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 return match matches.get_many::(options::FILE) { - Some(files) => perform_checksum_validation(files.map(OsStr::new), strict), - None => perform_checksum_validation(iter::once(OsStr::new("-")), strict), + Some(files) => perform_checksum_validation(files.map(OsStr::new), strict, algo_option), + None => perform_checksum_validation(iter::once(OsStr::new("-")), strict, algo_option), }; } diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 7587fc51f..71fc1dece 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -472,6 +472,21 @@ fn test_all_algorithms_fail_on_folder() { } } +#[cfg(unix)] +#[test] +fn test_check_error_incorrect_format() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("checksum", "e5773576fc75ff0f8eba14f61587ae28 README.md"); + + scene + .ucmd() + .arg("-c") + .arg("checksum") + .fails() + .stderr_contains("no properly formatted checksum lines found"); +} + #[cfg(unix)] #[test] fn test_dev_null() { @@ -674,13 +689,13 @@ fn test_check_algo_err() { scene .ucmd() - .arg("--a") + .arg("-a") .arg("sm3") .arg("--check") .arg("f") .fails() .no_stdout() - .stderr_contains("cksum: no properly formatted checksum lines found") + .stderr_contains("cksum: f: no properly formatted checksum lines found") .code_is(1); } @@ -705,14 +720,16 @@ fn test_cksum_check() { .arg("--check") .arg("CHECKSUM") .succeeds() - .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n"); + .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n") + .stderr_does_not_contain("line is improperly formatted"); scene .ucmd() .arg("--check") .arg("--strict") .arg("CHECKSUM") .succeeds() - .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n"); + .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n") + .stderr_does_not_contain("line is improperly formatted"); // inject invalid content at.append("CHECKSUM", "incorrect data"); scene @@ -805,4 +822,127 @@ fn test_cksum_check_failed() { .stderr_str() .contains("1 listed file could not be read\n")); assert!(result.stdout_str().contains("f: OK\n")); + + // tests with two files + at.touch("CHECKSUM2"); + at.write("f2", "42"); + for command in &commands { + let result = scene.ucmd().args(command).arg("f2").succeeds(); + at.append("CHECKSUM2", result.stdout_str()); + } + // inject invalid content + at.append("CHECKSUM2", "again incorrect data\naze\nSM3 (input2) = 7cfb120d4fabea2a904948538a438fdb57c725157cb40b5aee8d937b8351477e\n"); + at.append("CHECKSUM2", "again incorrect data\naze\nSM3 (input2) = 7cfb120d4fabea2a904948538a438fdb57c725157cb40b5aee8d937b8351477e\n"); + + let result = scene + .ucmd() + .arg("--check") + .arg("CHECKSUM") + .arg("CHECKSUM2") + .fails(); + println!("result.stderr_str() {}", result.stderr_str()); + println!("result.stdout_str() {}", result.stdout_str()); + assert!(result.stderr_str().contains("Failed to open file: input2")); + assert!(result + .stderr_str() + .contains("4 lines are improperly formatted\n")); + assert!(result + .stderr_str() + .contains("2 listed files could not be read\n")); + assert!(result.stdout_str().contains("f: OK\n")); + assert!(result.stdout_str().contains("2: OK\n")); +} + +#[test] +fn test_check_md5_format() { + let scene = TestScenario::new(util_name!()); + + let at = &scene.fixtures; + at.touch("empty"); + at.write("f", "d41d8cd98f00b204e9800998ecf8427e *empty\n"); + + scene + .ucmd() + .arg("-a") + .arg("md5") + .arg("--check") + .arg("f") + .succeeds() + .stdout_contains("empty: OK"); + + // with a second file + at.write("not-empty", "42"); + at.write("f2", "a1d0c6e83f027327d8461063f4ac58a6 *not-empty\n"); + + scene + .ucmd() + .arg("-a") + .arg("md5") + .arg("--check") + .arg("f") + .arg("f2") + .succeeds() + .stdout_contains("empty: OK") + .stdout_contains("not-empty: OK"); +} + +// Manage the mixed behavior +// cksum --check -a sm3 CHECKSUMS +// when CHECKSUM contains among other lines: +// SHA384 (input) = f392fd0ae43879ced890c665a1d47179116b5eddf6fb5b49f4982746418afdcbd54ba5eedcd422af3592f57f666da285 +#[test] +fn test_cksum_mixed() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let commands = [ + vec!["-a", "sha384"], + vec!["-a", "blake2b"], + vec!["-a", "blake2b", "-l", "384"], + vec!["-a", "sm3"], + ]; + at.touch("f"); + at.touch("CHECKSUM"); + for command in &commands { + let result = scene.ucmd().args(command).arg("f").succeeds(); + at.append("CHECKSUM", result.stdout_str()); + } + scene + .ucmd() + .arg("--check") + .arg("-a") + .arg("sm3") + .arg("CHECKSUM") + .succeeds() + .stdout_contains("f: OK") + .stderr_contains("3 lines are improperly formatted"); +} + +#[test] +fn test_cksum_garbage() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + // Incorrect data at the start + at.write( + "check-file", + "garbage MD5 (README.md) = e5773576fc75ff0f8eba14f61587ae28", + ); + scene + .ucmd() + .arg("--check") + .arg("check-file") + .fails() + .stderr_contains("check-file: no properly formatted checksum lines found"); + + // Incorrect data at the end + at.write( + "check-file", + "MD5 (README.md) = e5773576fc75ff0f8eba14f61587ae28 garbage", + ); + scene + .ucmd() + .arg("--check") + .arg("check-file") + .fails() + .stderr_contains("check-file: no properly formatted checksum lines found"); } From 8ddb2131dfb6870d4f7d0e2ab8aa766f14bc0e62 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 14 May 2024 13:13:35 +0200 Subject: [PATCH 06/15] cksum/blake2b, when length=512, don't add it in the line --- src/uu/cksum/src/cksum.rs | 8 +++++++- tests/by-util/test_cksum.rs | 30 +++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 9f6ed3bec..1b6ace6da 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -376,7 +376,13 @@ fn calculate_length(algo_name: &str, length: usize) -> UResult> { n => { if algo_name == ALGORITHM_OPTIONS_BLAKE2B { // Divide by 8, as our blake2b implementation expects bytes instead of bits. - Ok(Some(n / 8)) + if n == 512 { + // When length is 512, it is blake2b's default. + // So, don't show it + Ok(None) + } else { + Ok(Some(n / 8)) + } } else { Err(io::Error::new( io::ErrorKind::InvalidInput, diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 71fc1dece..62a182a7f 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -491,9 +491,6 @@ fn test_check_error_incorrect_format() { #[test] fn test_dev_null() { let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - at.touch("f"); scene .ucmd() @@ -505,6 +502,33 @@ fn test_dev_null() { .stdout_contains("d41d8cd98f00b204e9800998ecf8427e "); } +#[cfg(unix)] +#[test] +fn test_blake2b_512() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + + scene + .ucmd() + .arg("-a") + .arg("blake2b") + .arg("-l512") + .arg("f") + .succeeds() + .stdout_contains("BLAKE2b (f) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"); + + // test also the read + at.write("checksum", "BLAKE2b (f) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"); + scene + .ucmd() + .arg("--check") + .arg("checksum") + .succeeds() + .stdout_contains("f: OK"); +} + #[test] fn test_reset_binary() { let scene = TestScenario::new(util_name!()); From 843275a136b34c4bf1d9745c5942e16bd16a8e5c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 14 May 2024 13:32:49 +0200 Subject: [PATCH 07/15] cksum/blake2b: improve the error management --- src/uu/cksum/src/cksum.rs | 52 +++++++++---------- tests/by-util/test_cksum.rs | 6 ++- .../fixtures/cksum/supported_length.expected | 2 - .../cksum/unsupported_length.expected | 2 - 4 files changed, 29 insertions(+), 33 deletions(-) delete mode 100644 tests/fixtures/cksum/supported_length.expected delete mode 100644 tests/fixtures/cksum/unsupported_length.expected diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 1b6ace6da..00576d94e 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -358,7 +358,7 @@ fn had_reset(args: &[String]) -> bool { } /// Calculates the length of the digest for the given algorithm. -fn calculate_length(algo_name: &str, length: usize) -> UResult> { +fn calculate_blake2b_length(length: usize) -> UResult> { match length { 0 => Ok(None), n if n % 8 != 0 => { @@ -374,21 +374,13 @@ fn calculate_length(algo_name: &str, length: usize) -> UResult> { .into()) } n => { - if algo_name == ALGORITHM_OPTIONS_BLAKE2B { - // 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)) - } + // 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 { - Err(io::Error::new( - io::ErrorKind::InvalidInput, - "--length is only supported with --algorithm=blake2b", - ) - .into()) + Ok(Some(n / 8)) } } } @@ -623,7 +615,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let input_length = matches.get_one::(options::LENGTH); let length = match input_length { - Some(length) => calculate_length(algo_name, *length)?, + Some(length) => { + if algo_name != ALGORITHM_OPTIONS_BLAKE2B { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "--length is only supported with --algorithm=blake2b", + ) + .into()); + } else { + calculate_blake2b_length(*length)? + } + } None => None, }; @@ -766,7 +768,7 @@ pub fn uu_app() -> Command { #[cfg(test)] mod tests { use super::had_reset; - use crate::calculate_length; + use crate::calculate_blake2b_length; use crate::prompt_asterisk; #[test] @@ -836,17 +838,13 @@ mod tests { #[test] fn test_calculate_length() { - assert_eq!( - calculate_length(crate::ALGORITHM_OPTIONS_BLAKE2B, 256).unwrap(), - Some(32) - ); + assert_eq!(calculate_blake2b_length(256).unwrap(), Some(32)); + assert_eq!(calculate_blake2b_length(512).unwrap(), None); + assert_eq!(calculate_blake2b_length(256).unwrap(), Some(32)); + calculate_blake2b_length(255).unwrap_err(); - calculate_length(crate::ALGORITHM_OPTIONS_BLAKE2B, 255).unwrap_err(); + calculate_blake2b_length(33).unwrap_err(); - calculate_length(crate::ALGORITHM_OPTIONS_SHA256, 33).unwrap_err(); - - calculate_length(crate::ALGORITHM_OPTIONS_BLAKE2B, 513).unwrap_err(); - - calculate_length(crate::ALGORITHM_OPTIONS_SHA256, 256).unwrap_err(); + calculate_blake2b_length(513).unwrap_err(); } } diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 62a182a7f..0a15f4ef3 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -325,7 +325,7 @@ fn test_length_not_supported() { .arg("lorem_ipsum.txt") .fails() .no_stdout() - .stderr_is_fixture("unsupported_length.expected") + .stderr_contains("--length is only supported with --algorithm=blake2b") .code_is(1); } @@ -337,7 +337,9 @@ fn test_length() { .arg("lorem_ipsum.txt") .arg("alice_in_wonderland.txt") .succeeds() - .stdout_is_fixture("supported_length.expected"); + .stdout_contains( + "BLAKE2b-16 (lorem_ipsum.txt) = 7e2f\nBLAKE2b-16 (alice_in_wonderland.txt) = a546", + ); } #[test] diff --git a/tests/fixtures/cksum/supported_length.expected b/tests/fixtures/cksum/supported_length.expected deleted file mode 100644 index a2edb23e9..000000000 --- a/tests/fixtures/cksum/supported_length.expected +++ /dev/null @@ -1,2 +0,0 @@ -BLAKE2b-16 (lorem_ipsum.txt) = 7e2f -BLAKE2b-16 (alice_in_wonderland.txt) = a546 diff --git a/tests/fixtures/cksum/unsupported_length.expected b/tests/fixtures/cksum/unsupported_length.expected deleted file mode 100644 index c2a05fec7..000000000 --- a/tests/fixtures/cksum/unsupported_length.expected +++ /dev/null @@ -1,2 +0,0 @@ -cksum: invalid length: ‘15’ -cksum: length is not a multiple of 8 From bbd80e4061499f62faf3b0c27b866b6590860a10 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 14 May 2024 19:17:19 +0200 Subject: [PATCH 08/15] cksum: various improvements/fixes --- src/uu/cksum/src/cksum.rs | 58 ++++++++++++++++++++---------------- tests/by-util/test_cksum.rs | 59 ++++++++++++++++++++++++++++++------- 2 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 00576d94e..b23550616 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::checksum::cksum_output; +use uucore::display::Quotable; use uucore::error::set_exit_code; use uucore::{ encoding, @@ -419,8 +420,9 @@ where { // Regexp to handle the two input formats: // 1. [-] () = + // algo must be uppercase or b (for blake2b) // 2. [* ] - let regex_pattern = r"^(?P\w+)(-(?P\d+))?\s?\((?P.*)\) = (?P[a-fA-F0-9]+)$|^(?P[a-fA-F0-9]+)\s[* ](?P.*)"; + let regex_pattern = r"^(?P[A-Z0-9b]+)(-(?P\d+))?\s?\((?P.*)\) = (?P[a-fA-F0-9]+)$|^(?P[a-fA-F0-9]+)\s[* ](?P.*)"; let re = Regex::new(regex_pattern).unwrap(); // if cksum has several input files, it will print the result for each file @@ -474,7 +476,7 @@ where .as_str(); // If the algo_name is provided, we use it, otherwise we try to detect it - let algo_details = if algo_based_format { + let (algo_name, length) = if algo_based_format { // When the algo-based format is matched, extract details from regex captures let algorithm = caps.name("algo").map_or("", |m| m.as_str()).to_lowercase(); let bits = caps @@ -488,18 +490,18 @@ where // Default case if no algorithm is specified and non-algo based format is matched (String::new(), None) }; - if algo_based_format - && algo_name_input.map_or(false, |input| algo_details.0 != input) - { + + if algo_based_format && algo_name_input.map_or(false, |input| algo_name != input) { bad_format += 1; continue; } - if algo_details.0.is_empty() { + + if algo_name.is_empty() { // we haven't been able to detect the algo name. No point to continue properly_formatted = false; continue; } - let (_, mut algo, bits) = detect_algo(&algo_details.0, algo_details.1); + let (_, mut algo, bits) = detect_algo(&algo_name, length); // manage the input file let file_to_check: Box = if filename_to_check == "-" { @@ -538,9 +540,15 @@ where // not a single line correctly formatted found // return an error if !properly_formatted { + let filename = filename_input.to_string_lossy(); uucore::show_error!( "{}: no properly formatted checksum lines found", - filename_input.to_string_lossy() + if input_is_stdin { + "standard input" + } else { + &filename + } + .maybe_quote() ); set_exit_code(1); } @@ -586,6 +594,23 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .into()); } + let input_length = matches.get_one::(options::LENGTH); + + let length = match input_length { + Some(length) => { + if algo_name == ALGORITHM_OPTIONS_BLAKE2B { + calculate_blake2b_length(*length)? + } else { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "--length is only supported with --algorithm=blake2b", + ) + .into()); + } + } + None => None, + }; + if check { let text_flag: bool = matches.get_flag(options::TEXT); let binary_flag: bool = matches.get_flag(options::BINARY); @@ -612,23 +637,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; } - let input_length = matches.get_one::(options::LENGTH); - - let length = match input_length { - Some(length) => { - if algo_name != ALGORITHM_OPTIONS_BLAKE2B { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "--length is only supported with --algorithm=blake2b", - ) - .into()); - } else { - calculate_blake2b_length(*length)? - } - } - None => None, - }; - let (tag, asterisk) = handle_tag_text_binary_flags(&matches)?; let (name, algo, bits) = detect_algo(algo_name, length); diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 0a15f4ef3..de53a19a9 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -316,6 +316,16 @@ fn test_length_with_wrong_algorithm() { .no_stdout() .stderr_contains("cksum: --length is only supported with --algorithm=blake2b") .code_is(1); + + new_ucmd!() + .arg("--length=16") + .arg("--algorithm=md5") + .arg("-c") + .arg("foo.sums") + .fails() + .no_stdout() + .stderr_contains("cksum: --length is only supported with --algorithm=blake2b") + .code_is(1); } #[test] @@ -725,22 +735,36 @@ fn test_check_algo_err() { .code_is(1); } +#[test] +fn test_check_pipe() { + let scene = TestScenario::new(util_name!()); + + let at = &scene.fixtures; + + at.touch("f"); + + scene + .ucmd() + .arg("--check") + .arg("-") + .pipe_in("f") + .fails() + .no_stdout() + .stderr_contains("cksum: 'standard input': no properly formatted checksum lines found") + .code_is(1); +} + #[test] fn test_cksum_check() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - let commands = [ - vec!["-a", "sha384"], - vec!["-a", "blake2b"], - vec!["-a", "blake2b", "-l", "384"], - vec!["-a", "sm3"], - ]; + at.touch("f"); - at.touch("CHECKSUM"); - for command in &commands { - let result = scene.ucmd().args(command).arg("f").succeeds(); - at.append("CHECKSUM", result.stdout_str()); - } + at.write("CHECKSUM", "\ + SHA384 (f) = 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b\n\ + BLAKE2b (f) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce\n\ + BLAKE2b-384 (f) = b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100\n\ + SM3 (f) = 1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b\n"); scene .ucmd() .arg("--check") @@ -775,6 +799,19 @@ fn test_cksum_check() { .stderr_contains("line is improperly formatted"); } +#[test] +fn test_cksum_check_case() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write( + "CHECKSUM", + "Sha1 (f) = da39a3ee5e6b4b0d3255bfef95601890afd80709\n", + ); + scene.ucmd().arg("--check").arg("CHECKSUM").fails(); +} + #[test] fn test_cksum_check_invalid() { let scene = TestScenario::new(util_name!()); From 07d579c40aef5575989a72f44d2bb4a7b0bdd6e7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 14 May 2024 22:13:01 +0200 Subject: [PATCH 09/15] cksum: handle the empty lines (and just spaces) --- src/uu/cksum/src/cksum.rs | 3 +++ tests/by-util/test_cksum.rs | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index b23550616..127e932c1 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -533,6 +533,9 @@ where failed_cksum += 1; } } else { + if line.is_empty() { + continue; + } bad_format += 1; } } diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index de53a19a9..e60ed7e94 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -754,6 +754,46 @@ fn test_check_pipe() { .code_is(1); } +#[test] +fn test_cksum_check_empty_line() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("CHECKSUM", "\ + SHA384 (f) = 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b\n\ + BLAKE2b (f) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce\n\ + BLAKE2b-384 (f) = b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100\n\ + SM3 (f) = 1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b\n\n"); + scene + .ucmd() + .arg("--check") + .arg("CHECKSUM") + .succeeds() + .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n") + .stderr_does_not_contain("line is improperly formatted"); +} + +#[test] +fn test_cksum_check_space() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("CHECKSUM", "\ + SHA384 (f) = 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b\n\ + BLAKE2b (f) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce\n\ + BLAKE2b-384 (f) = b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100\n\ + SM3 (f) = 1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b\n \n"); + scene + .ucmd() + .arg("--check") + .arg("CHECKSUM") + .succeeds() + .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n") + .stderr_contains("line is improperly formatted"); +} + #[test] fn test_cksum_check() { let scene = TestScenario::new(util_name!()); From 115b203b22377e8ae4dcc24fb6e32503fbb6d86c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 14 May 2024 23:02:06 +0200 Subject: [PATCH 10/15] cksum: also show the error on stdout --- src/uu/cksum/src/cksum.rs | 7 +++---- tests/by-util/test_cksum.rs | 21 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 127e932c1..8645f2d4e 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -510,10 +510,9 @@ where match File::open(filename_to_check) { Ok(f) => Box::new(f), Err(err) => { - show!(err.map_err_context(|| format!( - "Failed to open file: {}", - filename_to_check - ))); + // yes, we have both stderr and stdout here + show!(err.map_err_context(|| filename_to_check.to_string())); + println!("{}: FAILED open or read", filename_to_check); failed_open_file += 1; // we could not open the file but we want to continue continue; diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index e60ed7e94..0e5d7f7f7 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -80,6 +80,21 @@ fn test_nonexisting_file() { .stderr_contains(format!("cksum: {file_name}: No such file or directory")); } +#[test] +fn test_nonexisting_file_out() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write( + "f", + "MD5 (nonexistent) = e5773576fc75ff0f8eba14f61587ae28\n", + ); + + ucmd.arg("-c") + .arg("f") + .fails() + .stdout_contains("nonexistent: FAILED open or read") + .stderr_contains("cksum: nonexistent: No such file or directory"); +} + #[test] fn test_one_nonexisting_file() { let (at, mut ucmd) = at_and_ucmd!(); @@ -905,7 +920,7 @@ fn test_cksum_check_failed() { .arg("CHECKSUM") .fails(); - assert!(result.stderr_str().contains("Failed to open file: input")); + assert!(result.stderr_str().contains("input: No such file or directory")); assert!(result .stderr_str() .contains("2 lines are improperly formatted\n")); @@ -917,7 +932,7 @@ fn test_cksum_check_failed() { // without strict let result = scene.ucmd().arg("--check").arg("CHECKSUM").fails(); - assert!(result.stderr_str().contains("Failed to open file: input")); + assert!(result.stderr_str().contains("input: No such file or directory")); assert!(result .stderr_str() .contains("2 lines are improperly formatted\n")); @@ -945,7 +960,7 @@ fn test_cksum_check_failed() { .fails(); println!("result.stderr_str() {}", result.stderr_str()); println!("result.stdout_str() {}", result.stdout_str()); - assert!(result.stderr_str().contains("Failed to open file: input2")); + assert!(result.stderr_str().contains("input2: No such file or directory")); assert!(result .stderr_str() .contains("4 lines are improperly formatted\n")); From 58c2b5d421409c5898ac17692e7f2010e7c87656 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 14 May 2024 23:16:09 +0200 Subject: [PATCH 11/15] cksum: add failing tests - to be fixed --- tests/by-util/test_cksum.rs | 59 +++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 0e5d7f7f7..bdb044899 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -920,7 +920,9 @@ fn test_cksum_check_failed() { .arg("CHECKSUM") .fails(); - assert!(result.stderr_str().contains("input: No such file or directory")); + assert!(result + .stderr_str() + .contains("input: No such file or directory")); assert!(result .stderr_str() .contains("2 lines are improperly formatted\n")); @@ -932,7 +934,9 @@ fn test_cksum_check_failed() { // without strict let result = scene.ucmd().arg("--check").arg("CHECKSUM").fails(); - assert!(result.stderr_str().contains("input: No such file or directory")); + assert!(result + .stderr_str() + .contains("input: No such file or directory")); assert!(result .stderr_str() .contains("2 lines are improperly formatted\n")); @@ -960,7 +964,9 @@ fn test_cksum_check_failed() { .fails(); println!("result.stderr_str() {}", result.stderr_str()); println!("result.stdout_str() {}", result.stdout_str()); - assert!(result.stderr_str().contains("input2: No such file or directory")); + assert!(result + .stderr_str() + .contains("input2: No such file or directory")); assert!(result .stderr_str() .contains("4 lines are improperly formatted\n")); @@ -1064,3 +1070,50 @@ fn test_cksum_garbage() { .fails() .stderr_contains("check-file: no properly formatted checksum lines found"); } + +#[ignore = "Should fail on bits"] +#[test] +fn test_md5_bits() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write( + "f", + "MD5-65536 (README.md) = e5773576fc75ff0f8eba14f61587ae28\n", + ); + + ucmd.arg("-c") + .arg("f") + .fails() + .stderr_contains("f: no properly formatted checksum lines found"); +} + +#[ignore = "Should fail on bits"] +#[test] +fn test_blake2b_bits() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write( + "f", + "BLAKE2b-257 (README.md) = f9a984b70cf9a7549920864860fd1131c9fb6c0552def0b6dcce1d87b4ec4c5d\n" + ); + + ucmd.arg("-c") + .arg("f") + .fails() + .stderr_contains("f: no properly formatted checksum lines found"); +} + +#[ignore = "Different output"] +#[test] +fn test_blake2d_tested_with_sha1() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write( + "f", + "BLAKE2b (f) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce\n" + ); + + ucmd.arg("-a") + .arg("sha1") + .arg("-c") + .arg("f") + .fails() + .stderr_contains("f: no properly formatted checksum lines found"); +} From 9c52ca8d55deacb07d4922664aa104283a4ba8dc Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 15 May 2024 11:46:58 +0200 Subject: [PATCH 12/15] cksum: also handle the case when the line start with ' \' --- src/uu/cksum/src/cksum.rs | 2 +- tests/by-util/test_cksum.rs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 8645f2d4e..6e244d375 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -422,7 +422,7 @@ where // 1. [-] () = // algo must be uppercase or b (for blake2b) // 2. [* ] - let regex_pattern = r"^(?P[A-Z0-9b]+)(-(?P\d+))?\s?\((?P.*)\) = (?P[a-fA-F0-9]+)$|^(?P[a-fA-F0-9]+)\s[* ](?P.*)"; + let regex_pattern = r"^\s*\\?(?P[A-Z0-9b]+)(-(?P\d+))?\s?\((?P.*)\) = (?P[a-fA-F0-9]+)$|^(?P[a-fA-F0-9]+)\s[* ](?P.*)"; let re = Regex::new(regex_pattern).unwrap(); // if cksum has several input files, it will print the result for each file diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index bdb044899..9072aff86 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -809,6 +809,25 @@ fn test_cksum_check_space() { .stderr_contains("line is improperly formatted"); } +#[test] +fn test_cksum_check_leading_info() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("CHECKSUM", "\ + \\SHA384 (f) = 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b\n\ + \\BLAKE2b (f) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce\n\ + \\BLAKE2b-384 (f) = b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100\n\ + \\SM3 (f) = 1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b\n"); + scene + .ucmd() + .arg("--check") + .arg("CHECKSUM") + .succeeds() + .stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n"); +} + #[test] fn test_cksum_check() { let scene = TestScenario::new(util_name!()); From 1b78102a6b7c6bdbfdd36c8c97c1d84788a8de21 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 17 May 2024 20:21:14 +0200 Subject: [PATCH 13/15] cksum: fails in case of unknown algo --- src/uu/cksum/src/cksum.rs | 33 ++++++++++++++++++++------------- tests/by-util/test_cksum.rs | 11 +++++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 6e244d375..54b6d13c9 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -43,6 +43,20 @@ const ALGORITHM_OPTIONS_SHA512: &str = "sha512"; const ALGORITHM_OPTIONS_BLAKE2B: &str = "blake2b"; const ALGORITHM_OPTIONS_SM3: &str = "sm3"; +const SUPPORTED_ALGO: [&str; 11] = [ + ALGORITHM_OPTIONS_SYSV, + ALGORITHM_OPTIONS_BSD, + ALGORITHM_OPTIONS_CRC, + ALGORITHM_OPTIONS_MD5, + ALGORITHM_OPTIONS_SHA1, + ALGORITHM_OPTIONS_SHA224, + ALGORITHM_OPTIONS_SHA256, + ALGORITHM_OPTIONS_SHA384, + ALGORITHM_OPTIONS_SHA512, + ALGORITHM_OPTIONS_BLAKE2B, + ALGORITHM_OPTIONS_SM3, +]; + #[derive(Debug)] enum CkSumError { RawMultipleFiles, @@ -479,6 +493,11 @@ where let (algo_name, length) = if algo_based_format { // When the algo-based format is matched, extract details from regex captures let algorithm = caps.name("algo").map_or("", |m| m.as_str()).to_lowercase(); + if !SUPPORTED_ALGO.contains(&algorithm.as_str()) { + // Not supported algo, leave early + properly_formatted = false; + continue; + } let bits = caps .name("bits") .map(|m| m.as_str().parse::().unwrap() / 8); @@ -688,19 +707,7 @@ pub fn uu_app() -> Command { .short('a') .help("select the digest type to use. See DIGEST below") .value_name("ALGORITHM") - .value_parser([ - ALGORITHM_OPTIONS_SYSV, - ALGORITHM_OPTIONS_BSD, - ALGORITHM_OPTIONS_CRC, - ALGORITHM_OPTIONS_MD5, - ALGORITHM_OPTIONS_SHA1, - ALGORITHM_OPTIONS_SHA224, - ALGORITHM_OPTIONS_SHA256, - ALGORITHM_OPTIONS_SHA384, - ALGORITHM_OPTIONS_SHA512, - ALGORITHM_OPTIONS_BLAKE2B, - ALGORITHM_OPTIONS_SM3, - ]), + .value_parser(SUPPORTED_ALGO), ) .arg( Arg::new(options::UNTAGGED) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 9072aff86..3002753d8 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -1136,3 +1136,14 @@ fn test_blake2d_tested_with_sha1() { .fails() .stderr_contains("f: no properly formatted checksum lines found"); } + +#[test] +fn test_unknown_sha() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write("f", "SHA4 (README.md) = 00000000\n"); + + ucmd.arg("-c") + .arg("f") + .fails() + .stderr_contains("f: no properly formatted checksum lines found"); +} From db58d2b6b53466e1250c23c6d051621316b1cb10 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 17 May 2024 21:06:32 +0200 Subject: [PATCH 14/15] cksum: when length/bits can't be divided by 8, generate an error --- src/uu/cksum/src/cksum.rs | 22 ++++++++++++++++++---- tests/by-util/test_cksum.rs | 1 - 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 54b6d13c9..554fe8514 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -498,10 +498,24 @@ where properly_formatted = false; continue; } - let bits = caps - .name("bits") - .map(|m| m.as_str().parse::().unwrap() / 8); - (algorithm, bits) + + let bits = caps.name("bits").map_or(Some(None), |m| { + let bits_value = m.as_str().parse::().unwrap(); + if bits_value % 8 == 0 { + Some(Some(bits_value / 8)) + } else { + properly_formatted = false; + None // Return None to signal a parsing or divisibility issue + } + }); + + if bits.is_none() { + // If bits is None, we have a parsing or divisibility issue + // Exit the loop outside of the closure + continue; + } + + (algorithm, bits.unwrap()) } else if let Some(a) = algo_name_input { // When a specific algorithm name is input, use it and default bits to None (a.to_lowercase(), None) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 3002753d8..2bfe1b3b8 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -1105,7 +1105,6 @@ fn test_md5_bits() { .stderr_contains("f: no properly formatted checksum lines found"); } -#[ignore = "Should fail on bits"] #[test] fn test_blake2b_bits() { let (at, mut ucmd) = at_and_ucmd!(); From 514d8103714d13e9771cab41936cab21b3b1b3ec Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 17 May 2024 22:09:41 +0200 Subject: [PATCH 15/15] cksum: improve the blake2b detection algo corner case --- src/uu/cksum/src/cksum.rs | 2 +- tests/by-util/test_cksum.rs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 554fe8514..9268a40bb 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -436,7 +436,7 @@ where // 1. [-] () = // algo must be uppercase or b (for blake2b) // 2. [* ] - let regex_pattern = r"^\s*\\?(?P[A-Z0-9b]+)(-(?P\d+))?\s?\((?P.*)\) = (?P[a-fA-F0-9]+)$|^(?P[a-fA-F0-9]+)\s[* ](?P.*)"; + let regex_pattern = r"^\s*\\?(?P(?:[A-Z0-9]+|BLAKE2b))(?:-(?P\d+))?\s?\((?P.*)\) = (?P[a-fA-F0-9]+)$|^(?P[a-fA-F0-9]+)\s[* ](?P.*)"; let re = Regex::new(regex_pattern).unwrap(); // if cksum has several input files, it will print the result for each file diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 2bfe1b3b8..19ea2badd 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -1119,6 +1119,28 @@ fn test_blake2b_bits() { .stderr_contains("f: no properly formatted checksum lines found"); } +#[test] +fn test_bsd_case() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("f", "bSD (README.md) = 0000\n"); + + scene + .ucmd() + .arg("-c") + .arg("f") + .fails() + .stderr_contains("f: no properly formatted checksum lines found"); + at.write("f", "BsD (README.md) = 0000\n"); + + scene + .ucmd() + .arg("-c") + .arg("f") + .fails() + .stderr_contains("f: no properly formatted checksum lines found"); +} + #[ignore = "Different output"] #[test] fn test_blake2d_tested_with_sha1() {