From 94204c0a2c0a74e9e98015592c1bbe374f36d312 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:04:48 +0000 Subject: [PATCH 1/6] Initial plan From b0fa034aeb1b5627b6e7fedaeea49df112ac6621 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:10:14 +0000 Subject: [PATCH 2/6] Add safe_parse module with radix validation helpers Co-authored-by: jgarzik <494411+jgarzik@users.noreply.github.com> --- plib/src/lib.rs | 1 + plib/src/safe_parse.rs | 167 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 plib/src/safe_parse.rs diff --git a/plib/src/lib.rs b/plib/src/lib.rs index 8c3cf37e..8ad2e558 100644 --- a/plib/src/lib.rs +++ b/plib/src/lib.rs @@ -15,6 +15,7 @@ pub mod modestr; pub mod platform; pub mod priority; pub mod regex; +pub mod safe_parse; pub mod sccsfile; pub mod testing; pub mod user; diff --git a/plib/src/safe_parse.rs b/plib/src/safe_parse.rs new file mode 100644 index 00000000..3eb9ebcd --- /dev/null +++ b/plib/src/safe_parse.rs @@ -0,0 +1,167 @@ +// +// Copyright (c) 2024 Hemi Labs, Inc. +// +// This file is part of the posixutils-rs project covered under +// the MIT License. For the full license text, please see the LICENSE +// file in the root directory of this project. +// SPDX-License-Identifier: MIT +// + +//! Safe parsing utilities for integers with radix validation. +//! +//! This module provides helper functions to safely parse integers from strings +//! with specific radixes, addressing the issue that Rust's `from_str_radix` +//! accepts leading `+` or `-` signs even for non-decimal radixes. +//! +//! For example, `i64::from_str_radix("+55", 16)` succeeds, which may be +//! undesirable when parsing user input that should strictly conform to +//! hexadecimal or octal format without signs. + +use std::num::ParseIntError; + +/// Safely parse an unsigned integer from a string with the given radix. +/// +/// This function validates that the input string contains only valid digits +/// for the specified radix, rejecting any leading signs (`+` or `-`). +/// +/// # Examples +/// +/// ``` +/// use plib::safe_parse::parse_u32_radix; +/// +/// assert!(parse_u32_radix("ff", 16).is_ok()); +/// assert!(parse_u32_radix("+ff", 16).is_err()); // Reject leading + +/// assert!(parse_u32_radix("377", 8).is_ok()); +/// assert!(parse_u32_radix("+377", 8).is_err()); // Reject leading + +/// ``` +pub fn parse_u32_radix(s: &str, radix: u32) -> Result { + if s.is_empty() { + return Err("empty string".parse::().unwrap_err()); + } + + // Check for leading signs + if s.starts_with('+') || s.starts_with('-') { + return Err("leading sign not allowed".parse::().unwrap_err()); + } + + u32::from_str_radix(s, radix) +} + +/// Safely parse a signed integer from a string with the given radix. +/// +/// This function is similar to `parse_u32_radix`, but allows a leading `-` +/// sign for negative numbers. It still rejects a leading `+` sign for +/// non-decimal radixes to ensure strict format validation. +/// +/// # Examples +/// +/// ``` +/// use plib::safe_parse::parse_i64_radix; +/// +/// assert!(parse_i64_radix("ff", 16).is_ok()); +/// assert!(parse_i64_radix("-ff", 16).is_ok()); // Allow leading - +/// assert!(parse_i64_radix("+ff", 16).is_err()); // Reject leading + +/// ``` +pub fn parse_i64_radix(s: &str, radix: u32) -> Result { + if s.is_empty() { + return Err("empty string".parse::().unwrap_err()); + } + + // For non-decimal radixes, reject leading + sign + if radix != 10 && s.starts_with('+') { + return Err("leading + sign not allowed".parse::().unwrap_err()); + } + + i64::from_str_radix(s, radix) +} + +/// Safely parse a u64 from a string with the given radix. +pub fn parse_u64_radix(s: &str, radix: u32) -> Result { + if s.is_empty() { + return Err("empty string".parse::().unwrap_err()); + } + + if s.starts_with('+') || s.starts_with('-') { + return Err("leading sign not allowed".parse::().unwrap_err()); + } + + u64::from_str_radix(s, radix) +} + +/// Safely parse a usize from a string with the given radix. +pub fn parse_usize_radix(s: &str, radix: u32) -> Result { + if s.is_empty() { + return Err("empty string".parse::().unwrap_err()); + } + + if s.starts_with('+') || s.starts_with('-') { + return Err("leading sign not allowed".parse::().unwrap_err()); + } + + usize::from_str_radix(s, radix) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_u32_radix_hex() { + // Valid hex + assert_eq!(parse_u32_radix("ff", 16).unwrap(), 255); + assert_eq!(parse_u32_radix("FF", 16).unwrap(), 255); + assert_eq!(parse_u32_radix("0", 16).unwrap(), 0); + + // Invalid - leading signs + assert!(parse_u32_radix("+ff", 16).is_err()); + assert!(parse_u32_radix("-ff", 16).is_err()); + + // Invalid - non-hex digits + assert!(parse_u32_radix("gg", 16).is_err()); + + // Invalid - empty + assert!(parse_u32_radix("", 16).is_err()); + } + + #[test] + fn test_parse_u32_radix_octal() { + // Valid octal + assert_eq!(parse_u32_radix("377", 8).unwrap(), 255); + assert_eq!(parse_u32_radix("0", 8).unwrap(), 0); + + // Invalid - leading signs + assert!(parse_u32_radix("+377", 8).is_err()); + assert!(parse_u32_radix("-377", 8).is_err()); + + // Invalid - non-octal digits + assert!(parse_u32_radix("389", 8).is_err()); + } + + #[test] + fn test_parse_i64_radix_hex() { + // Valid hex + assert_eq!(parse_i64_radix("ff", 16).unwrap(), 255); + assert_eq!(parse_i64_radix("-ff", 16).unwrap(), -255); + + // Invalid - leading + sign + assert!(parse_i64_radix("+ff", 16).is_err()); + } + + #[test] + fn test_parse_i64_radix_octal() { + // Valid octal + assert_eq!(parse_i64_radix("377", 8).unwrap(), 255); + assert_eq!(parse_i64_radix("-377", 8).unwrap(), -255); + + // Invalid - leading + sign + assert!(parse_i64_radix("+377", 8).is_err()); + } + + #[test] + fn test_parse_i64_radix_decimal() { + // Decimal allows + sign + assert_eq!(parse_i64_radix("123", 10).unwrap(), 123); + assert_eq!(parse_i64_radix("+123", 10).unwrap(), 123); + assert_eq!(parse_i64_radix("-123", 10).unwrap(), -123); + } +} From dd2832c0b780256257f8ce59492c619a1fcf3631 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:15:02 +0000 Subject: [PATCH 3/6] Fix from_str_radix misuse in user input parsing Co-authored-by: jgarzik <494411+jgarzik@users.noreply.github.com> --- display/printf.rs | 8 ++++++++ display/tests/printf/mod.rs | 23 +++++++++++++++++++++++ file/find.rs | 12 +++++++----- file/od.rs | 19 +++++++++++++++++-- plib/src/modestr.rs | 7 +++++-- sh/builtin/umask.rs | 4 ++++ sys/ipcrm.rs | 4 ++++ text/tr.rs | 5 +++++ 8 files changed, 73 insertions(+), 9 deletions(-) diff --git a/display/printf.rs b/display/printf.rs index c9358512..20543929 100644 --- a/display/printf.rs +++ b/display/printf.rs @@ -840,6 +840,10 @@ fn parse_integer_arg(arg: &[u8]) -> Result<(i64, bool), String> { .or_else(|| num_str.strip_prefix("0X")) { // Hexadecimal + // Reject if hex part starts with a sign (after we already handled the sign above) + if hex.starts_with('+') || hex.starts_with('-') { + return Err(format!("invalid number: {arg_str}")); + } match i64::from_str_radix(hex, 16) { Ok(n) => (n, true), Err(_) => { @@ -863,6 +867,10 @@ fn parse_integer_arg(arg: &[u8]) -> Result<(i64, bool), String> { .unwrap_or(false) { // Octal + // Reject if octal part starts with a sign (after we already handled the sign above) + if num_str.starts_with('+') || num_str.starts_with('-') { + return Err(format!("invalid number: {arg_str}")); + } let valid_len = num_str .chars() .take_while(|c| ('0'..='7').contains(c)) diff --git a/display/tests/printf/mod.rs b/display/tests/printf/mod.rs index 8ef68055..f7d0df79 100644 --- a/display/tests/printf/mod.rs +++ b/display/tests/printf/mod.rs @@ -532,3 +532,26 @@ fn test_float_missing_arg() { fn test_float_char_constant() { printf_test(&["%f", "'A"], "65.000000"); } + +// Regression tests for from_str_radix security issue +// These should fail because double signs are invalid +#[test] +#[should_panic] +fn test_reject_double_plus_hex() { + // This should fail: "+0x+55" should be rejected, not parsed as 0x55 + printf_test(&["%d", "+0x+55"], "85"); +} + +#[test] +#[should_panic] +fn test_reject_double_minus_hex() { + // This should fail: "-0x-55" should be rejected + printf_test(&["%d", "-0x-55"], "-85"); +} + +#[test] +#[should_panic] +fn test_reject_double_plus_octal() { + // This should fail: "+0+55" should be rejected + printf_test(&["%d", "+0+55"], "45"); +} diff --git a/file/find.rs b/file/find.rs index 93fab9dd..c26eb983 100644 --- a/file/find.rs +++ b/file/find.rs @@ -476,12 +476,14 @@ fn parse_perm_mode(mode_str: &str) -> Result { /// Parse a mode value (octal or symbolic) fn parse_mode_value(mode_str: &str) -> Result { - // Try octal first - if let Ok(m) = u32::from_str_radix(mode_str, 8) { - if m > 0o7777 { - return Err(format!("invalid mode: {}", mode_str)); + // Try octal first, but reject if it starts with a sign + if !mode_str.starts_with('+') && !mode_str.starts_with('-') { + if let Ok(m) = u32::from_str_radix(mode_str, 8) { + if m > 0o7777 { + return Err(format!("invalid mode: {}", mode_str)); + } + return Ok(m); } - return Ok(m); } // Parse as symbolic mode starting from 0 diff --git a/file/od.rs b/file/od.rs index 0e9a6406..eea1a30d 100644 --- a/file/od.rs +++ b/file/od.rs @@ -219,9 +219,19 @@ fn parse_count + FromStrRadix>( count: &str, ) -> Result> { if count.starts_with("0x") || count.starts_with("0X") { - T::from_str_radix(&count[2..], 16).map_err(|e| Box::new(e) as Box) + let hex_part = &count[2..]; + // Reject if hex part contains a sign + if hex_part.starts_with('+') || hex_part.starts_with('-') { + return Err("invalid hexadecimal number".into()); + } + T::from_str_radix(hex_part, 16).map_err(|e| Box::new(e) as Box) } else if count.starts_with('0') && count.len() > 1 { - T::from_str_radix(&count[1..], 8).map_err(|e| Box::new(e) as Box) + let oct_part = &count[1..]; + // Reject if octal part contains a sign + if oct_part.starts_with('+') || oct_part.starts_with('-') { + return Err("invalid octal number".into()); + } + T::from_str_radix(oct_part, 8).map_err(|e| Box::new(e) as Box) } else { count .parse::() @@ -279,6 +289,11 @@ fn parse_offset(offset: &str) -> Result { offset }; + // Reject if offset contains a sign (offsets should be unsigned) + if offset.starts_with('+') || offset.starts_with('-') { + return Err("invalid offset".parse::().unwrap_err()); + } + let parsed_offset = u64::from_str_radix(offset, base)?; Ok(parsed_offset * multiplier) diff --git a/plib/src/modestr.rs b/plib/src/modestr.rs index 68498c00..ab84874b 100644 --- a/plib/src/modestr.rs +++ b/plib/src/modestr.rs @@ -72,8 +72,11 @@ enum ParseState { } pub fn parse(mode: &str) -> Result { - if let Ok(m) = u32::from_str_radix(mode, 8) { - return Ok(ChmodMode::Absolute(m, mode.len() as u32)); + // Try parsing as octal, but reject if it starts with a sign + if !mode.starts_with('+') && !mode.starts_with('-') { + if let Ok(m) = u32::from_str_radix(mode, 8) { + return Ok(ChmodMode::Absolute(m, mode.len() as u32)); + } } let mut done_with_char; diff --git a/sh/builtin/umask.rs b/sh/builtin/umask.rs index 99ab04f7..f05a2af5 100644 --- a/sh/builtin/umask.rs +++ b/sh/builtin/umask.rs @@ -56,6 +56,10 @@ impl BuiltinUtility for Umask { } else if option_parser.next_argument() == args.len() - 1 { // TODO: support symbolic umask let new_umask = &args[option_parser.next_argument()]; + // Reject if umask contains a sign + if new_umask.starts_with('+') || new_umask.starts_with('-') { + return Err(format!("umask: invalid mask '{new_umask}'").into()); + } let new_umask = u32::from_str_radix(new_umask, 8) .map_err(|_| format!("umask: invalid mask '{new_umask}'"))?; if new_umask > 0o777 { diff --git a/sys/ipcrm.rs b/sys/ipcrm.rs index 69053f56..fc08a6ae 100644 --- a/sys/ipcrm.rs +++ b/sys/ipcrm.rs @@ -20,6 +20,10 @@ use libc::{semctl, semget, shmctl, shmget, shmid_ds}; /// Parse an IPC key value that may be decimal or hexadecimal (0x prefix) fn parse_ipc_key(s: &str) -> Result { if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { + // Reject if hex part starts with a sign + if hex.starts_with('+') || hex.starts_with('-') { + return Err("invalid hex key: unexpected sign after 0x prefix".to_string()); + } u32::from_str_radix(hex, 16) .map(|v| v as i32) .map_err(|e| format!("invalid hex key: {}", e)) diff --git a/text/tr.rs b/text/tr.rs index 86c9761d..2c60e087 100644 --- a/text/tr.rs +++ b/text/tr.rs @@ -625,6 +625,11 @@ mod parsing { let char_repetition = match repeat_string.as_str() { "" => CharRepetition::AsManyAsNeeded, st => { + // Reject if repeat count starts with a sign + if st.starts_with('+') || st.starts_with('-') { + return Err(format!("invalid repeat count '{st}' in [c*n] construct",)); + } + let radix = if st.starts_with('0') { // Octal 8_u32 From fee60dcf76a8ddcd7e83518b7b14c324598cabae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:19:16 +0000 Subject: [PATCH 4/6] Add defense-in-depth fixes for archive and file format parsing Co-authored-by: jgarzik <494411+jgarzik@users.noreply.github.com> --- pax/formats/cpio.rs | 7 ++++++- pax/formats/pax.rs | 4 ++++ pax/formats/ustar.rs | 4 ++++ pax/main.rs | 5 +++++ pax/modes/append.rs | 9 +++++++++ pax/multivolume.rs | 4 ++++ xform/uudecode.rs | 7 ++++++- 7 files changed, 38 insertions(+), 2 deletions(-) diff --git a/pax/formats/cpio.rs b/pax/formats/cpio.rs index 181b1a7e..741f2702 100644 --- a/pax/formats/cpio.rs +++ b/pax/formats/cpio.rs @@ -272,7 +272,12 @@ fn parse_header(header: &[u8; HEADER_SIZE], reader: &mut R) -> PaxResul fn parse_octal_field(bytes: &[u8]) -> PaxResult { let s = std::str::from_utf8(bytes) .map_err(|_| PaxError::InvalidHeader("invalid octal field".to_string()))?; - u64::from_str_radix(s.trim(), 8) + let s = s.trim(); + // Reject if the octal string contains a sign + if s.starts_with('+') || s.starts_with('-') { + return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s))); + } + u64::from_str_radix(s, 8) .map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s))) } diff --git a/pax/formats/pax.rs b/pax/formats/pax.rs index 8bc75aa2..1a030297 100644 --- a/pax/formats/pax.rs +++ b/pax/formats/pax.rs @@ -837,6 +837,10 @@ fn parse_octal(bytes: &[u8]) -> PaxResult { if s.is_empty() { return Ok(0); } + // Reject if the octal string contains a sign + if s.starts_with('+') || s.starts_with('-') { + return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s))); + } u64::from_str_radix(&s, 8).map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s))) } diff --git a/pax/formats/ustar.rs b/pax/formats/ustar.rs index 84862b18..fc1add95 100644 --- a/pax/formats/ustar.rs +++ b/pax/formats/ustar.rs @@ -273,6 +273,10 @@ fn parse_octal(bytes: &[u8]) -> PaxResult { if s.is_empty() { return Ok(0); } + // Reject if the octal string contains a sign + if s.starts_with('+') || s.starts_with('-') { + return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s))); + } u64::from_str_radix(&s, 8).map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s))) } diff --git a/pax/main.rs b/pax/main.rs index 063df8fc..69666e8c 100644 --- a/pax/main.rs +++ b/pax/main.rs @@ -557,6 +557,11 @@ fn is_valid_tar_checksum(buf: &[u8]) -> bool { return false; } + // Reject if checksum contains a sign + if chksum_str.starts_with('+') || chksum_str.starts_with('-') { + return false; + } + let stored = match u32::from_str_radix(chksum_str, 8) { Ok(v) => v, Err(_) => return false, diff --git a/pax/modes/append.rs b/pax/modes/append.rs index 94b135e5..1687ff9a 100644 --- a/pax/modes/append.rs +++ b/pax/modes/append.rs @@ -124,6 +124,11 @@ fn is_valid_tar_checksum(header: &[u8]) -> bool { return false; } + // Reject if checksum contains a sign + if chksum_str.starts_with('+') || chksum_str.starts_with('-') { + return false; + } + let stored = match u32::from_str_radix(chksum_str, 8) { Ok(v) => v, Err(_) => return false, @@ -214,6 +219,10 @@ fn parse_octal(bytes: &[u8]) -> PaxResult { if s.is_empty() { return Ok(0); } + // Reject if the octal string contains a sign + if s.starts_with('+') || s.starts_with('-') { + return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s))); + } u64::from_str_radix(s, 8).map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s))) } diff --git a/pax/multivolume.rs b/pax/multivolume.rs index 575286c8..0f66910c 100644 --- a/pax/multivolume.rs +++ b/pax/multivolume.rs @@ -745,6 +745,10 @@ fn parse_octal(bytes: &[u8]) -> PaxResult { if s.is_empty() { return Ok(0); } + // Reject if the octal string contains a sign + if s.starts_with('+') || s.starts_with('-') { + return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s))); + } u64::from_str_radix(s, 8).map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s))) } diff --git a/xform/uudecode.rs b/xform/uudecode.rs index 6d0571da..6424553f 100644 --- a/xform/uudecode.rs +++ b/xform/uudecode.rs @@ -59,7 +59,12 @@ impl Header { panic!("Invalid encoding type"); }; - let lower_perm_bits = u32::from_str_radix(split[1], 8).expect("Invalid permission value"); + let mode_str = split[1]; + // Reject if mode contains a sign + if mode_str.starts_with('+') || mode_str.starts_with('-') { + panic!("Invalid permission value: unexpected sign"); + } + let lower_perm_bits = u32::from_str_radix(mode_str, 8).expect("Invalid permission value"); let out = PathBuf::from(split[2]); Self { From 0a98e2dc48464ae326804966dff40cc27901a4eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:41:37 +0000 Subject: [PATCH 5/6] Remove safe_parse module as requested in review Co-authored-by: jgarzik <494411+jgarzik@users.noreply.github.com> --- plib/src/lib.rs | 1 - plib/src/safe_parse.rs | 167 ----------------------------------------- 2 files changed, 168 deletions(-) delete mode 100644 plib/src/safe_parse.rs diff --git a/plib/src/lib.rs b/plib/src/lib.rs index 8ad2e558..8c3cf37e 100644 --- a/plib/src/lib.rs +++ b/plib/src/lib.rs @@ -15,7 +15,6 @@ pub mod modestr; pub mod platform; pub mod priority; pub mod regex; -pub mod safe_parse; pub mod sccsfile; pub mod testing; pub mod user; diff --git a/plib/src/safe_parse.rs b/plib/src/safe_parse.rs deleted file mode 100644 index 3eb9ebcd..00000000 --- a/plib/src/safe_parse.rs +++ /dev/null @@ -1,167 +0,0 @@ -// -// Copyright (c) 2024 Hemi Labs, Inc. -// -// This file is part of the posixutils-rs project covered under -// the MIT License. For the full license text, please see the LICENSE -// file in the root directory of this project. -// SPDX-License-Identifier: MIT -// - -//! Safe parsing utilities for integers with radix validation. -//! -//! This module provides helper functions to safely parse integers from strings -//! with specific radixes, addressing the issue that Rust's `from_str_radix` -//! accepts leading `+` or `-` signs even for non-decimal radixes. -//! -//! For example, `i64::from_str_radix("+55", 16)` succeeds, which may be -//! undesirable when parsing user input that should strictly conform to -//! hexadecimal or octal format without signs. - -use std::num::ParseIntError; - -/// Safely parse an unsigned integer from a string with the given radix. -/// -/// This function validates that the input string contains only valid digits -/// for the specified radix, rejecting any leading signs (`+` or `-`). -/// -/// # Examples -/// -/// ``` -/// use plib::safe_parse::parse_u32_radix; -/// -/// assert!(parse_u32_radix("ff", 16).is_ok()); -/// assert!(parse_u32_radix("+ff", 16).is_err()); // Reject leading + -/// assert!(parse_u32_radix("377", 8).is_ok()); -/// assert!(parse_u32_radix("+377", 8).is_err()); // Reject leading + -/// ``` -pub fn parse_u32_radix(s: &str, radix: u32) -> Result { - if s.is_empty() { - return Err("empty string".parse::().unwrap_err()); - } - - // Check for leading signs - if s.starts_with('+') || s.starts_with('-') { - return Err("leading sign not allowed".parse::().unwrap_err()); - } - - u32::from_str_radix(s, radix) -} - -/// Safely parse a signed integer from a string with the given radix. -/// -/// This function is similar to `parse_u32_radix`, but allows a leading `-` -/// sign for negative numbers. It still rejects a leading `+` sign for -/// non-decimal radixes to ensure strict format validation. -/// -/// # Examples -/// -/// ``` -/// use plib::safe_parse::parse_i64_radix; -/// -/// assert!(parse_i64_radix("ff", 16).is_ok()); -/// assert!(parse_i64_radix("-ff", 16).is_ok()); // Allow leading - -/// assert!(parse_i64_radix("+ff", 16).is_err()); // Reject leading + -/// ``` -pub fn parse_i64_radix(s: &str, radix: u32) -> Result { - if s.is_empty() { - return Err("empty string".parse::().unwrap_err()); - } - - // For non-decimal radixes, reject leading + sign - if radix != 10 && s.starts_with('+') { - return Err("leading + sign not allowed".parse::().unwrap_err()); - } - - i64::from_str_radix(s, radix) -} - -/// Safely parse a u64 from a string with the given radix. -pub fn parse_u64_radix(s: &str, radix: u32) -> Result { - if s.is_empty() { - return Err("empty string".parse::().unwrap_err()); - } - - if s.starts_with('+') || s.starts_with('-') { - return Err("leading sign not allowed".parse::().unwrap_err()); - } - - u64::from_str_radix(s, radix) -} - -/// Safely parse a usize from a string with the given radix. -pub fn parse_usize_radix(s: &str, radix: u32) -> Result { - if s.is_empty() { - return Err("empty string".parse::().unwrap_err()); - } - - if s.starts_with('+') || s.starts_with('-') { - return Err("leading sign not allowed".parse::().unwrap_err()); - } - - usize::from_str_radix(s, radix) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_u32_radix_hex() { - // Valid hex - assert_eq!(parse_u32_radix("ff", 16).unwrap(), 255); - assert_eq!(parse_u32_radix("FF", 16).unwrap(), 255); - assert_eq!(parse_u32_radix("0", 16).unwrap(), 0); - - // Invalid - leading signs - assert!(parse_u32_radix("+ff", 16).is_err()); - assert!(parse_u32_radix("-ff", 16).is_err()); - - // Invalid - non-hex digits - assert!(parse_u32_radix("gg", 16).is_err()); - - // Invalid - empty - assert!(parse_u32_radix("", 16).is_err()); - } - - #[test] - fn test_parse_u32_radix_octal() { - // Valid octal - assert_eq!(parse_u32_radix("377", 8).unwrap(), 255); - assert_eq!(parse_u32_radix("0", 8).unwrap(), 0); - - // Invalid - leading signs - assert!(parse_u32_radix("+377", 8).is_err()); - assert!(parse_u32_radix("-377", 8).is_err()); - - // Invalid - non-octal digits - assert!(parse_u32_radix("389", 8).is_err()); - } - - #[test] - fn test_parse_i64_radix_hex() { - // Valid hex - assert_eq!(parse_i64_radix("ff", 16).unwrap(), 255); - assert_eq!(parse_i64_radix("-ff", 16).unwrap(), -255); - - // Invalid - leading + sign - assert!(parse_i64_radix("+ff", 16).is_err()); - } - - #[test] - fn test_parse_i64_radix_octal() { - // Valid octal - assert_eq!(parse_i64_radix("377", 8).unwrap(), 255); - assert_eq!(parse_i64_radix("-377", 8).unwrap(), -255); - - // Invalid - leading + sign - assert!(parse_i64_radix("+377", 8).is_err()); - } - - #[test] - fn test_parse_i64_radix_decimal() { - // Decimal allows + sign - assert_eq!(parse_i64_radix("123", 10).unwrap(), 123); - assert_eq!(parse_i64_radix("+123", 10).unwrap(), 123); - assert_eq!(parse_i64_radix("-123", 10).unwrap(), -123); - } -} From 0766f69c764bcc59f08660e1c970dd8d4734833c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:47:35 +0000 Subject: [PATCH 6/6] Run cargo fmt to fix formatting issues Co-authored-by: jgarzik <494411+jgarzik@users.noreply.github.com> --- pax/formats/cpio.rs | 3 +-- text/tr.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pax/formats/cpio.rs b/pax/formats/cpio.rs index 741f2702..78752bfc 100644 --- a/pax/formats/cpio.rs +++ b/pax/formats/cpio.rs @@ -277,8 +277,7 @@ fn parse_octal_field(bytes: &[u8]) -> PaxResult { if s.starts_with('+') || s.starts_with('-') { return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s))); } - u64::from_str_radix(s, 8) - .map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s))) + u64::from_str_radix(s, 8).map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s))) } /// Parse filename, removing NUL terminator diff --git a/text/tr.rs b/text/tr.rs index 2c60e087..4e8ff3fd 100644 --- a/text/tr.rs +++ b/text/tr.rs @@ -629,7 +629,7 @@ mod parsing { if st.starts_with('+') || st.starts_with('-') { return Err(format!("invalid repeat count '{st}' in [c*n] construct",)); } - + let radix = if st.starts_with('0') { // Octal 8_u32