diff --git a/src/uucore/src/lib/features/format/num_parser.rs b/src/uucore/src/lib/features/format/num_parser.rs index f7a72bccd..b19c3d72f 100644 --- a/src/uucore/src/lib/features/format/num_parser.rs +++ b/src/uucore/src/lib/features/format/num_parser.rs @@ -181,11 +181,13 @@ impl ParsedNumber { }; } + let trimmed_input = input.trim_ascii_start(); + // Initial minus sign - let (negative, unsigned) = if let Some(input) = input.strip_prefix('-') { - (true, input) + let (negative, unsigned) = if let Some(trimmed_input) = trimmed_input.strip_prefix('-') { + (true, trimmed_input) } else { - (false, input) + (false, trimmed_input) }; // Parse an optional base prefix ("0b" / "0B" / "0" / "0x" / "0X"). "0" is octal unless a @@ -384,4 +386,39 @@ mod tests { assert_eq!(Ok(0b1011), ParsedNumber::parse_u64("0b1011")); assert_eq!(Ok(0b1011), ParsedNumber::parse_u64("0B1011")); } + + #[test] + fn test_parsing_with_leading_whitespace() { + assert_eq!(Ok(1), ParsedNumber::parse_u64(" 0x1")); + assert_eq!(Ok(-2), ParsedNumber::parse_i64(" -0x2")); + assert_eq!(Ok(-3), ParsedNumber::parse_i64(" \t-0x3")); + assert_eq!(Ok(-4), ParsedNumber::parse_i64(" \n-0x4")); + assert_eq!(Ok(-5), ParsedNumber::parse_i64(" \n\t\u{000d}-0x5")); + + // Ensure that trailing whitespace is still a partial match + assert_eq!( + Err(ParseError::PartialMatch(6, " ")), + ParsedNumber::parse_u64("0x6 ") + ); + assert_eq!( + Err(ParseError::PartialMatch(7, "\t")), + ParsedNumber::parse_u64("0x7\t") + ); + assert_eq!( + Err(ParseError::PartialMatch(8, "\n")), + ParsedNumber::parse_u64("0x8\n") + ); + + // Ensure that unicode non-ascii whitespace is a partial match + assert_eq!( + Err(ParseError::NotNumeric), + ParsedNumber::parse_i64("\u{2029}-0x9") + ); + + // Ensure that whitespace after the number has "started" is not allowed + assert_eq!( + Err(ParseError::NotNumeric), + ParsedNumber::parse_i64("- 0x9") + ); + } } diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 9597d1130..be9826d92 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -1053,3 +1053,29 @@ fn float_switch_switch_decimal_scientific() { .succeeds() .stdout_only("1e-05"); } + +#[test] +fn float_arg_with_whitespace() { + new_ucmd!() + .args(&["%f", " \u{0020}\u{000d}\t\n0.000001"]) + .succeeds() + .stdout_only("0.000001"); + + new_ucmd!() + .args(&["%f", "0.1 "]) + .fails() + .stderr_contains("value not completely converted"); + + // Unicode whitespace should not be allowed in a number + new_ucmd!() + .args(&["%f", "\u{2029}0.1"]) + .fails() + .stderr_contains("expected a numeric value"); + + // A input string with a whitespace special character that has + // not already been expanded should fail. + new_ucmd!() + .args(&["%f", "\\t0.1"]) + .fails() + .stderr_contains("expected a numeric value"); +}