mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
cksum/hashsum: add support of --check with base64
This commit is contained in:
parent
32c5d23f91
commit
66ccb1a479
2 changed files with 90 additions and 1 deletions
|
@ -4,6 +4,7 @@
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
// spell-checker:ignore anotherfile invalidchecksum
|
// spell-checker:ignore anotherfile invalidchecksum
|
||||||
|
|
||||||
|
use data_encoding::BASE64;
|
||||||
use os_display::Quotable;
|
use os_display::Quotable;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -24,6 +25,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
util_name,
|
util_name,
|
||||||
};
|
};
|
||||||
|
use quick_error::quick_error;
|
||||||
use std::io::stdin;
|
use std::io::stdin;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
|
|
||||||
|
@ -314,6 +316,8 @@ pub fn detect_algo(algo: &str, length: Option<usize>) -> UResult<HashAlgorithm>
|
||||||
// 2. <checksum> [* ]<filename>
|
// 2. <checksum> [* ]<filename>
|
||||||
// 3. <checksum> [*]<filename> (only one space)
|
// 3. <checksum> [*]<filename> (only one space)
|
||||||
const ALGO_BASED_REGEX: &str = r"^\s*\\?(?P<algo>(?:[A-Z0-9]+|BLAKE2b))(?:-(?P<bits>\d+))?\s?\((?P<filename>.*)\)\s*=\s*(?P<checksum>[a-fA-F0-9]+)$";
|
const ALGO_BASED_REGEX: &str = r"^\s*\\?(?P<algo>(?:[A-Z0-9]+|BLAKE2b))(?:-(?P<bits>\d+))?\s?\((?P<filename>.*)\)\s*=\s*(?P<checksum>[a-fA-F0-9]+)$";
|
||||||
|
const ALGO_BASED_REGEX_BASE64: &str = r"^\s*\\?(?P<algo>(?:[A-Z0-9]+|BLAKE2b))(?:-(?P<bits>\d+))?\s?\((?P<filename>.*)\)\s*=\s*(?P<checksum>[A-Za-z0-9+/]+={0,2})$";
|
||||||
|
|
||||||
const DOUBLE_SPACE_REGEX: &str = r"^(?P<checksum>[a-fA-F0-9]+)\s{2}(?P<filename>.*)$";
|
const DOUBLE_SPACE_REGEX: &str = r"^(?P<checksum>[a-fA-F0-9]+)\s{2}(?P<filename>.*)$";
|
||||||
|
|
||||||
// In this case, we ignore the *
|
// In this case, we ignore the *
|
||||||
|
@ -338,6 +342,7 @@ fn determine_regex(
|
||||||
let algo_based_regex = Regex::new(ALGO_BASED_REGEX).unwrap();
|
let algo_based_regex = Regex::new(ALGO_BASED_REGEX).unwrap();
|
||||||
let double_space_regex = Regex::new(DOUBLE_SPACE_REGEX).unwrap();
|
let double_space_regex = Regex::new(DOUBLE_SPACE_REGEX).unwrap();
|
||||||
let single_space_regex = Regex::new(SINGLE_SPACE_REGEX).unwrap();
|
let single_space_regex = Regex::new(SINGLE_SPACE_REGEX).unwrap();
|
||||||
|
let algo_based_regex_base64 = Regex::new(ALGO_BASED_REGEX_BASE64).unwrap();
|
||||||
|
|
||||||
for line in lines {
|
for line in lines {
|
||||||
let line_trim = line.trim();
|
let line_trim = line.trim();
|
||||||
|
@ -347,6 +352,8 @@ fn determine_regex(
|
||||||
return Ok((double_space_regex, false));
|
return Ok((double_space_regex, false));
|
||||||
} else if single_space_regex.is_match(line_trim) {
|
} else if single_space_regex.is_match(line_trim) {
|
||||||
return Ok((single_space_regex, false));
|
return Ok((single_space_regex, false));
|
||||||
|
} else if algo_based_regex_base64.is_match(line_trim) {
|
||||||
|
return Ok((algo_based_regex_base64, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,6 +366,40 @@ fn determine_regex(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to convert bytes to a hexadecimal string
|
||||||
|
fn bytes_to_hex(bytes: &[u8]) -> String {
|
||||||
|
bytes
|
||||||
|
.iter()
|
||||||
|
.map(|byte| format!("{:02x}", byte))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get the expected checksum
|
||||||
|
|
||||||
|
fn get_expected_checksum(
|
||||||
|
filename: &str,
|
||||||
|
caps: ®ex::Captures,
|
||||||
|
chosen_regex: &Regex,
|
||||||
|
) -> UResult<String> {
|
||||||
|
if chosen_regex.as_str() == ALGO_BASED_REGEX_BASE64 {
|
||||||
|
let ck = caps.name("checksum").unwrap().as_str();
|
||||||
|
match BASE64.decode(ck.as_bytes()) {
|
||||||
|
Ok(decoded_bytes) => {
|
||||||
|
match std::str::from_utf8(&decoded_bytes) {
|
||||||
|
Ok(decoded_str) => Ok(decoded_str.to_string()),
|
||||||
|
Err(_) => Ok(bytes_to_hex(&decoded_bytes)), // Handle as raw bytes if not valid UTF-8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Err(Box::new(
|
||||||
|
ChecksumError::NoProperlyFormattedChecksumLinesFound((&filename).to_string()),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(caps.name("checksum").unwrap().as_str().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Do the checksum validation (can be strict or not)
|
* Do the checksum validation (can be strict or not)
|
||||||
*/
|
*/
|
||||||
|
@ -423,7 +464,8 @@ where
|
||||||
filename_to_check = &filename_to_check[1..];
|
filename_to_check = &filename_to_check[1..];
|
||||||
}
|
}
|
||||||
|
|
||||||
let expected_checksum = caps.name("checksum").unwrap().as_str();
|
let expected_checksum =
|
||||||
|
get_expected_checksum(&filename_to_check, &caps, &chosen_regex)?;
|
||||||
|
|
||||||
// If the algo_name is provided, we use it, otherwise we try to detect it
|
// If the algo_name is provided, we use it, otherwise we try to detect it
|
||||||
let (algo_name, length) = if is_algo_based_format {
|
let (algo_name, length) = if is_algo_based_format {
|
||||||
|
@ -964,4 +1006,31 @@ mod tests {
|
||||||
let result = determine_regex(filename, false, &lines_invalid);
|
let result = determine_regex(filename, false, &lines_invalid);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_expected_checksum() {
|
||||||
|
let re = Regex::new(ALGO_BASED_REGEX_BASE64).unwrap();
|
||||||
|
let caps = re
|
||||||
|
.captures("SHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = get_expected_checksum("filename", &caps, &re);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_expected_checksum_invalid() {
|
||||||
|
let re = Regex::new(ALGO_BASED_REGEX_BASE64).unwrap();
|
||||||
|
let caps = re
|
||||||
|
.captures("SHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = get_expected_checksum("filename", &caps, &re);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1201,3 +1201,23 @@ fn test_check_directory_error() {
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_contains(err_msg);
|
.stderr_contains(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_base64_hashes() {
|
||||||
|
let hashes =
|
||||||
|
"MD5 (empty) = 1B2M2Y8AsgTpgAmY7PhCfg==\nSHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\nBLAKE2b (empty) = eGoC90IBWQPGxv2FJVLScpEvR0DhWEdhiobiF/cfVBnSXhAxr+5YUxOJZESTTrBLkDpoWxRIt1XVb3Aa/pvizg==\n"
|
||||||
|
;
|
||||||
|
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
at.touch("empty");
|
||||||
|
at.write("check", hashes);
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("--check")
|
||||||
|
.arg(at.subdir.join("check"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("empty: OK\nempty: OK\nempty: OK\n");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue