Merge pull request #6390 from sylvestre/cksum-check

cksum: implement check (Closes: #5705)
This commit is contained in:
Ben Wiederhake 2024-05-18 23:46:58 +02:00 committed by GitHub
commit b718f954e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 855 additions and 116 deletions

1
Cargo.lock generated
View file

@ -2494,6 +2494,7 @@ version = "0.0.26"
dependencies = [
"clap",
"hex",
"regex",
"uucore",
]

View file

@ -16,8 +16,9 @@ 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 }
[[bin]]
name = "cksum"

View file

@ -5,15 +5,18 @@
// spell-checker:ignore (ToDO) fname, algo
use clap::{crate_version, value_parser, Arg, ArgAction, Command};
use hex::decode;
use hex::encode;
use regex::Regex;
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::checksum::cksum_output;
use uucore::display::Quotable;
use uucore::error::set_exit_code;
use uucore::{
encoding,
error::{FromIo, UError, UResult, USimpleError},
@ -40,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,
@ -73,10 +90,10 @@ impl Display for CkSumError {
}
fn detect_algo(
program: &str,
algo: &str,
length: Option<usize>,
) -> (&'static str, Box<dyn Digest + 'static>, usize) {
match program {
match algo {
ALGORITHM_OPTIONS_SYSV => (
ALGORITHM_OPTIONS_SYSV,
Box::new(SYSV::new()) as Box<dyn Digest>,
@ -205,7 +222,7 @@ where
ALGORITHM_OPTIONS_SYSV | ALGORITHM_OPTIONS_BSD => {
sum_hex.parse::<u16>().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 +231,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, &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 +317,7 @@ fn digest_read<T: 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))
}
}
@ -312,6 +330,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";
}
@ -353,17 +372,45 @@ fn had_reset(args: &[String]) -> bool {
}
}
/// Calculates the length of the digest for the given algorithm.
fn calculate_blake2b_length(length: usize) -> UResult<Option<usize>> {
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 => {
// 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))
}
}
}
}
/***
* cksum has a bunch of legacy behavior.
* 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<String> = std::env::args().collect();
@ -371,70 +418,209 @@ 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 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"the --binary and --text options are meaningless when verifying checksums",
)
.into());
}
Ok((tag, asterisk))
}
/***
* Do the checksum validation (can be strict or not)
*/
fn perform_checksum_validation<'a, I>(
files: I,
strict: bool,
algo_name_input: Option<&str>,
) -> UResult<()>
where
I: Iterator<Item = &'a OsStr>,
{
// Regexp to handle the two input formats:
// 1. <algo>[-<bits>] (<filename>) = <checksum>
// algo must be uppercase or b (for blake2b)
// 2. <checksum> [* ]<filename>
let regex_pattern = r"^\s*\\?(?P<algo>(?:[A-Z0-9]+|BLAKE2b))(?:-(?P<bits>\d+))?\s?\((?P<filename1>.*)\) = (?P<checksum1>[a-fA-F0-9]+)$|^(?P<checksum2>[a-fA-F0-9]+)\s[* ](?P<filename2>.*)";
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<dyn Read> = if input_is_stdin {
Box::new(stdin()) // Use stdin if "-" is specified
} else {
match File::open(filename_input) {
Ok(f) => Box::new(f),
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.unwrap_or_else(|_| String::new());
if let Some(caps) = re.captures(&line) {
properly_formatted = true;
// Determine what kind of file input we had
// we need it for case "--check -a sm3 <file>" when <file> is
// <algo>[-<bits>] (<filename>) = <checksum>
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_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_or(Some(None), |m| {
let bits_value = m.as_str().parse::<usize>().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)
} 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_name != input) {
bad_format += 1;
continue;
}
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_name, length);
// manage the input file
let file_to_check: Box<dyn Read> = 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) => {
// 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;
}
}
};
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();
// Do the checksum validation
if expected_checksum == calculated_checksum {
println!("{}: OK", filename_to_check);
} else {
println!("{}: FAILED", filename_to_check);
failed_cksum += 1;
}
} else {
if line.is_empty() {
continue;
}
bad_format += 1;
}
}
// 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",
if input_is_stdin {
"standard input"
} else {
&filename
}
.maybe_quote()
);
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);
}
Ok(())
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
let algo_name: &str = match matches.get_one::<String>(options::ALGORITHM) {
Some(v) => v,
None => ALGORITHM_OPTIONS_CRC,
};
let input_length = matches.get_one::<usize>(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)
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
}
}
} else {
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,
@ -443,6 +629,51 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.into());
}
let input_length = matches.get_one::<usize>(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);
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());
}
// 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::<String>(options::FILE) {
Some(files) => perform_checksum_validation(files.map(OsStr::new), strict, algo_option),
None => perform_checksum_validation(iter::once(OsStr::new("-")), strict, algo_option),
};
}
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) {
@ -490,19 +721,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)
@ -535,12 +754,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')
@ -580,6 +799,7 @@ pub fn uu_app() -> Command {
#[cfg(test)]
mod tests {
use super::had_reset;
use crate::calculate_blake2b_length;
use crate::prompt_asterisk;
#[test]
@ -646,4 +866,16 @@ mod tests {
assert!(prompt_asterisk(false, true, false));
assert!(!prompt_asterisk(false, false, false));
}
#[test]
fn test_calculate_length() {
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_blake2b_length(33).unwrap_err();
calculate_blake2b_length(513).unwrap_err();
}
}

View file

@ -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 }

View file

@ -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";
@ -835,35 +835,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(())

View file

@ -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"]

View file

@ -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")]

View file

@ -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);
}
}

View file

@ -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")]

View file

@ -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!();
@ -316,6 +331,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]
@ -325,7 +350,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 +362,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]
@ -474,11 +501,23 @@ fn test_all_algorithms_fail_on_folder() {
#[cfg(unix)]
#[test]
fn test_dev_null() {
fn test_check_error_incorrect_format() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("checksum", "e5773576fc75ff0f8eba14f61587ae28 README.md");
at.touch("f");
scene
.ucmd()
.arg("-c")
.arg("checksum")
.fails()
.stderr_contains("no properly formatted checksum lines found");
}
#[cfg(unix)]
#[test]
fn test_dev_null() {
let scene = TestScenario::new(util_name!());
scene
.ucmd()
@ -490,6 +529,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!());
@ -663,3 +729,442 @@ 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: f: no properly formatted checksum lines found")
.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_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_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!());
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")
.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")
.stderr_does_not_contain("line is improperly formatted");
// 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_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!());
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("input: No such file or directory"));
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("input: No such file or directory"));
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"));
// 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("input2: No such file or directory"));
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");
}
#[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");
}
#[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");
}
#[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() {
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");
}
#[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");
}

View file

@ -1,2 +0,0 @@
BLAKE2b-16 (lorem_ipsum.txt) = 7e2f
BLAKE2b-16 (alice_in_wonderland.txt) = a546

View file

@ -1,2 +0,0 @@
cksum: invalid length: 15
cksum: length is not a multiple of 8