Skip to content

Commit c224b0d

Browse files
committed
b2sum: better validation of the args like in tests/cksum/b2sum
1 parent 32eef06 commit c224b0d

10 files changed

Lines changed: 170 additions & 23 deletions

File tree

src/uu/cksum/locales/en-US.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ cksum-help-zero = end each output line with NUL, not newline, and disable file n
3333
# Error messages
3434
cksum-error-is-directory = { $file }: Is a directory
3535
cksum-error-failed-to-read-input = failed to read input
36+
cksum-error-invalid-length = invalid length: { $length }
37+
cksum-error-max-digest-length = maximum digest length for '{ $algorithm }' is { $max_bits } bits

src/uu/cksum/locales/fr-FR.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ cksum-help-zero = terminer chaque ligne de sortie avec NUL, pas un saut de ligne
3333
# Messages d'erreur
3434
cksum-error-is-directory = { $file } : Est un répertoire
3535
cksum-error-failed-to-read-input = échec de la lecture de l'entrée
36+
cksum-error-invalid-length = longueur invalide : { $length }
37+
cksum-error-max-digest-length = longueur maximale de condensé pour '{ $algorithm }' est { $max_bits } bits

src/uu/cksum/src/cksum.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// spell-checker:ignore (ToDO) fname, algo
77

88
use clap::builder::ValueParser;
9-
use clap::{Arg, ArgAction, Command, value_parser};
9+
use clap::{Arg, ArgAction, Command};
1010
use std::ffi::{OsStr, OsString};
1111
use std::fs::File;
1212
use std::io::{BufReader, Read, Write, stdin, stdout};
@@ -15,8 +15,8 @@ use std::path::Path;
1515
use uucore::checksum::{
1616
ALGORITHM_OPTIONS_BLAKE2B, ALGORITHM_OPTIONS_BSD, ALGORITHM_OPTIONS_CRC,
1717
ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_SYSV, ChecksumError, ChecksumOptions,
18-
ChecksumVerbose, SUPPORTED_ALGORITHMS, calculate_blake2b_length, detect_algo, digest_reader,
19-
perform_checksum_validation,
18+
ChecksumVerbose, SUPPORTED_ALGORITHMS, detect_algo, digest_reader, perform_checksum_validation,
19+
validate_blake2b_length_with_fluent,
2020
};
2121
use uucore::translate;
2222

@@ -250,12 +250,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
250250
}
251251
};
252252

253-
let input_length = matches.get_one::<usize>(options::LENGTH);
253+
let input_length = matches.get_one::<String>(options::LENGTH);
254254

255255
let length = match input_length {
256-
Some(length) => {
256+
Some(length_str) => {
257257
if algo_name == ALGORITHM_OPTIONS_BLAKE2B {
258-
calculate_blake2b_length(*length)?
258+
validate_blake2b_length_with_fluent(length_str, "cksum")?
259259
} else {
260260
return Err(ChecksumError::LengthOnlyForBlake2b.into());
261261
}
@@ -378,7 +378,7 @@ pub fn uu_app() -> Command {
378378
.arg(
379379
Arg::new(options::LENGTH)
380380
.long(options::LENGTH)
381-
.value_parser(value_parser!(usize))
381+
.value_parser(ValueParser::string())
382382
.short('l')
383383
.help(translate!("cksum-help-length"))
384384
.action(ArgAction::Set),

src/uu/hashsum/locales/en-US.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ hashsum-help-b3sum = work with BLAKE3
4040
4141
# Error messages
4242
hashsum-error-failed-to-read-input = failed to read input
43+
hashsum-error-invalid-length = invalid length: { $length }
44+
hashsum-error-max-digest-length = maximum digest length for '{ $algorithm }' is { $max_bits } bits

src/uu/hashsum/locales/fr-FR.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@ hashsum-help-b3sum = travailler avec BLAKE3
3737
3838
# Messages d'erreur
3939
hashsum-error-failed-to-read-input = échec de la lecture de l'entrée
40+
hashsum-error-invalid-length = longueur invalide : { $length }
41+
hashsum-error-max-digest-length = longueur maximale de condensé pour '{ $algorithm }' est { $max_bits } bits

src/uu/hashsum/src/hashsum.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
use clap::ArgAction;
99
use clap::builder::ValueParser;
10-
use clap::value_parser;
1110
use clap::{Arg, ArgMatches, Command};
1211
use std::ffi::{OsStr, OsString};
1312
use std::fs::File;
@@ -19,12 +18,12 @@ use uucore::checksum::ChecksumError;
1918
use uucore::checksum::ChecksumOptions;
2019
use uucore::checksum::ChecksumVerbose;
2120
use uucore::checksum::HashAlgorithm;
22-
use uucore::checksum::calculate_blake2b_length;
2321
use uucore::checksum::create_sha3;
2422
use uucore::checksum::detect_algo;
2523
use uucore::checksum::digest_reader;
2624
use uucore::checksum::escape_filename;
2725
use uucore::checksum::perform_checksum_validation;
26+
use uucore::checksum::validate_blake2b_length;
2827
use uucore::error::{FromIo, UResult};
2928
use uucore::format_usage;
3029
use uucore::sum::{Digest, Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256};
@@ -185,14 +184,14 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
185184
// least somewhat better from a user's perspective.
186185
let matches = uucore::clap_localization::handle_clap_result(command, args)?;
187186

188-
let input_length: Option<&usize> = if binary_name == "b2sum" {
189-
matches.get_one::<usize>(options::LENGTH)
187+
let input_length: Option<&String> = if binary_name == "b2sum" {
188+
matches.get_one::<String>(options::LENGTH)
190189
} else {
191190
None
192191
};
193192

194193
let length = match input_length {
195-
Some(length) => calculate_blake2b_length(*length)?,
194+
Some(length_str) => validate_blake2b_length(length_str, "hashsum")?,
196195
None => None,
197196
};
198197

@@ -427,7 +426,7 @@ fn uu_app_opt_length(command: Command) -> Command {
427426
command.arg(
428427
Arg::new(options::LENGTH)
429428
.long(options::LENGTH)
430-
.value_parser(value_parser!(usize))
429+
.value_parser(ValueParser::string())
431430
.short('l')
432431
.help(translate!("hashsum-help-length"))
433432
.overrides_with(options::LENGTH)

src/uucore/src/lib/features/checksum.rs

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use std::{
1717
};
1818

1919
use crate::{
20+
display::Quotable as DisplayQuotable,
2021
error::{FromIo, UError, UResult, USimpleError},
2122
os_str_as_bytes, os_str_from_bytes,
2223
quoting_style::{QuotingStyle, locale_aware_escape_name},
@@ -25,7 +26,7 @@ use crate::{
2526
Blake2b, Blake3, Bsd, CRC32B, Crc, Digest, DigestWriter, Md5, Sha1, Sha3_224, Sha3_256,
2627
Sha3_384, Sha3_512, Sha224, Sha256, Sha384, Sha512, Shake128, Shake256, Sm3, SysV,
2728
},
28-
util_name,
29+
translate, util_name,
2930
};
3031
use thiserror::Error;
3132

@@ -1185,22 +1186,47 @@ pub fn digest_reader<T: Read>(
11851186
}
11861187
}
11871188

1189+
/// Validates and calculates the length of the digest from a string input.
1190+
/// This function handles very large numbers that might not fit in usize.
1191+
pub fn validate_blake2b_length_str(length_str: &str) -> UResult<Option<usize>> {
1192+
// First try to parse as u128 to handle very large numbers
1193+
match length_str.parse::<u128>() {
1194+
Ok(length_u128) => {
1195+
if length_u128 > usize::MAX as u128 {
1196+
// For very large numbers, always show the max length error
1197+
show_error!("invalid length: '{length_str}'");
1198+
return Err(io::Error::new(
1199+
io::ErrorKind::InvalidInput,
1200+
"maximum digest length for 'BLAKE2b' is 512 bits",
1201+
)
1202+
.into());
1203+
}
1204+
let length = length_u128 as usize;
1205+
calculate_blake2b_length(length)
1206+
}
1207+
Err(_) => {
1208+
show_error!("invalid length: '{length_str}'");
1209+
Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid length").into())
1210+
}
1211+
}
1212+
}
1213+
11881214
/// Calculates the length of the digest.
11891215
pub fn calculate_blake2b_length(length: usize) -> UResult<Option<usize>> {
11901216
match length {
11911217
0 => Ok(None),
1192-
n if n % 8 != 0 => {
1193-
show_error!("invalid length: \u{2018}{length}\u{2019}");
1194-
Err(io::Error::new(io::ErrorKind::InvalidInput, "length is not a multiple of 8").into())
1195-
}
11961218
n if n > 512 => {
1197-
show_error!("invalid length: \u{2018}{length}\u{2019}");
1219+
show_error!("invalid length: '{length}'");
11981220
Err(io::Error::new(
11991221
io::ErrorKind::InvalidInput,
1200-
"maximum digest length for \u{2018}BLAKE2b\u{2019} is 512 bits",
1222+
"maximum digest length for 'BLAKE2b' is 512 bits",
12011223
)
12021224
.into())
12031225
}
1226+
n if n % 8 != 0 => {
1227+
show_error!("invalid length: '{length}'");
1228+
Err(io::Error::new(io::ErrorKind::InvalidInput, "length is not a multiple of 8").into())
1229+
}
12041230
n => {
12051231
// Divide by 8, as our blake2b implementation expects bytes instead of bits.
12061232
if n == 512 {
@@ -1214,6 +1240,78 @@ pub fn calculate_blake2b_length(length: usize) -> UResult<Option<usize>> {
12141240
}
12151241
}
12161242

1243+
/// Validate BLAKE2b length with Fluent error messages
1244+
/// This function is used by utilities that need localized error messages
1245+
pub fn validate_blake2b_length(
1246+
length_str: &str,
1247+
utility_name: &str,
1248+
) -> UResult<Option<usize>> {
1249+
// First try to parse as u128 to handle very large numbers
1250+
match length_str.parse::<u128>() {
1251+
Ok(length_u128) => {
1252+
if length_u128 > usize::MAX as u128 {
1253+
// For very large numbers, always show the max length error
1254+
// Use the original string to avoid precision issues
1255+
let error_key = format!("{}-error-invalid-length", utility_name);
1256+
let max_key = format!("{}-error-max-digest-length", utility_name);
1257+
show_error!(
1258+
"{}",
1259+
translate!(&error_key, "length" => DisplayQuotable::quote(length_str))
1260+
);
1261+
return Err(io::Error::new(
1262+
io::ErrorKind::InvalidInput,
1263+
translate!(&max_key, "algorithm" => "BLAKE2b", "max_bits" => 512),
1264+
)
1265+
.into());
1266+
}
1267+
let length = length_u128 as usize;
1268+
match length {
1269+
0 => Ok(None),
1270+
n if n > 512 => {
1271+
let error_key = format!("{}-error-invalid-length", utility_name);
1272+
let max_key = format!("{}-error-max-digest-length", utility_name);
1273+
show_error!(
1274+
"{}",
1275+
translate!(&error_key, "length" => DisplayQuotable::quote(length_str))
1276+
);
1277+
Err(io::Error::new(
1278+
io::ErrorKind::InvalidInput,
1279+
translate!(&max_key, "algorithm" => "BLAKE2b", "max_bits" => 512),
1280+
)
1281+
.into())
1282+
}
1283+
n if n % 8 != 0 => {
1284+
let error_key = format!("{}-error-invalid-length", utility_name);
1285+
show_error!(
1286+
"{}",
1287+
translate!(&error_key, "length" => DisplayQuotable::quote(length_str))
1288+
);
1289+
Err(io::Error::new(
1290+
io::ErrorKind::InvalidInput,
1291+
"length is not a multiple of 8",
1292+
)
1293+
.into())
1294+
}
1295+
n => {
1296+
// Divide by 8, as our blake2b implementation expects bytes instead of bits.
1297+
if n == 512 {
1298+
// When length is 512, it is blake2b's default.
1299+
// So, don't show it
1300+
Ok(None)
1301+
} else {
1302+
Ok(Some(n / 8))
1303+
}
1304+
}
1305+
}
1306+
}
1307+
Err(_) => {
1308+
let error_key = format!("{}-error-invalid-length", utility_name);
1309+
show_error!("{}", translate!(&error_key, "length" => length_str.quote()));
1310+
Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid length").into())
1311+
}
1312+
}
1313+
}
1314+
12171315
pub fn unescape_filename(filename: &[u8]) -> (Vec<u8>, &'static str) {
12181316
let mut unescaped = Vec::with_capacity(filename.len());
12191317
let mut byte_iter = filename.iter().peekable();

tests/by-util/test_cksum.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,31 @@ fn test_length_greater_than_512() {
394394
.stderr_is_fixture("length_larger_than_512.expected");
395395
}
396396

397+
#[test]
398+
fn test_length_513_shows_max_length_error() {
399+
// Test that length 513 shows "maximum digest length" error before "not multiple of 8" error
400+
new_ucmd!()
401+
.arg("--length=513")
402+
.arg("--algorithm=blake2b")
403+
.arg("/dev/null")
404+
.fails_with_code(1)
405+
.no_stdout()
406+
.stderr_contains("maximum digest length for 'BLAKE2b' is 512 bits");
407+
}
408+
409+
#[test]
410+
fn test_length_very_large_number() {
411+
// Test that very large numbers like UINTMAX_OFLOW show proper error messages
412+
new_ucmd!()
413+
.arg("--length=18446744073709551616")
414+
.arg("--algorithm=blake2b")
415+
.arg("/dev/null")
416+
.fails_with_code(1)
417+
.no_stdout()
418+
.stderr_contains("invalid length: '18446744073709551616'")
419+
.stderr_contains("maximum digest length for 'BLAKE2b' is 512 bits");
420+
}
421+
397422
#[test]
398423
fn test_length_is_zero() {
399424
new_ucmd!()

tests/by-util/test_hashsum.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,24 @@ fn test_invalid_b2sum_length_option_too_large() {
269269
.ccmd("b2sum")
270270
.arg("--length=513")
271271
.arg(at.subdir.join("testf"))
272-
.fails_with_code(1);
272+
.fails_with_code(1)
273+
.stderr_contains("maximum digest length for 'BLAKE2b' is 512 bits");
274+
}
275+
276+
#[test]
277+
fn test_invalid_b2sum_length_very_large_number() {
278+
let scene = TestScenario::new(util_name!());
279+
let at = &scene.fixtures;
280+
281+
at.write("testf", "foobar\n");
282+
283+
scene
284+
.ccmd("b2sum")
285+
.arg("--length=18446744073709551616")
286+
.arg(at.subdir.join("testf"))
287+
.fails_with_code(1)
288+
.stderr_contains("invalid length: '18446744073709551616'")
289+
.stderr_contains("maximum digest length for 'BLAKE2b' is 512 bits");
273290
}
274291

275292
#[test]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
cksum: invalid length: 1024
2-
cksum: maximum digest length for BLAKE2b is 512 bits
1+
cksum: invalid length: '1024'
2+
cksum: maximum digest length for 'BLAKE2b' is 512 bits

0 commit comments

Comments
 (0)