realpath: fix regression with empty string validation

Fixes issue introduced in b965c944837df66b233f57fca7275fbed4e4d311 where
switching from NonEmptyStringValueParser to OsString parser removed
validation that arguments cannot be empty strings.

- Add custom NonEmptyOsStringParser that validates OsString is not empty
- Use the parser for FILES, --relative-to, and --relative-base arguments
- Add test case to verify empty strings are rejected with exit code 1
- Fixes GNU realpath test failure
This commit is contained in:
Sylvestre Ledru 2025-08-09 19:02:49 +02:00
parent 1ab3a8df4f
commit a7f3cb0209
2 changed files with 62 additions and 5 deletions

View file

@ -5,9 +5,12 @@
// spell-checker:ignore (ToDO) retcode
use clap::{Arg, ArgAction, ArgMatches, Command};
use clap::{
Arg, ArgAction, ArgMatches, Command,
builder::{TypedValueParser, ValueParserFactory},
};
use std::{
ffi::OsString,
ffi::{OsStr, OsString},
io::{Write, stdout},
path::{Path, PathBuf},
};
@ -34,6 +37,39 @@ const OPT_RELATIVE_BASE: &str = "relative-base";
const ARG_FILES: &str = "files";
/// Custom parser that validates `OsString` is not empty
#[derive(Clone, Debug)]
struct NonEmptyOsStringParser;
impl TypedValueParser for NonEmptyOsStringParser {
type Value = OsString;
fn parse_ref(
&self,
_cmd: &Command,
_arg: Option<&Arg>,
value: &OsStr,
) -> Result<Self::Value, clap::Error> {
if value.is_empty() {
let mut err = clap::Error::new(clap::error::ErrorKind::ValueValidation);
err.insert(
clap::error::ContextKind::Custom,
clap::error::ContextValue::String("invalid operand: empty string".to_string()),
);
return Err(err);
}
Ok(value.to_os_string())
}
}
impl ValueParserFactory for NonEmptyOsStringParser {
type Parser = Self;
fn value_parser() -> Self::Parser {
Self
}
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args).with_exit_code(1)?;
@ -146,21 +182,21 @@ pub fn uu_app() -> Command {
Arg::new(OPT_RELATIVE_TO)
.long(OPT_RELATIVE_TO)
.value_name("DIR")
.value_parser(clap::value_parser!(OsString))
.value_parser(NonEmptyOsStringParser)
.help(translate!("realpath-help-relative-to")),
)
.arg(
Arg::new(OPT_RELATIVE_BASE)
.long(OPT_RELATIVE_BASE)
.value_name("DIR")
.value_parser(clap::value_parser!(OsString))
.value_parser(NonEmptyOsStringParser)
.help(translate!("realpath-help-relative-base")),
)
.arg(
Arg::new(ARG_FILES)
.action(ArgAction::Append)
.required(true)
.value_parser(clap::value_parser!(OsString))
.value_parser(NonEmptyOsStringParser)
.value_hint(clap::ValueHint::AnyPath),
)
}

View file

@ -489,3 +489,24 @@ fn test_realpath_non_utf8_paths() {
assert!(output.contains("test_"));
assert!(output.contains(".txt"));
}
#[test]
fn test_realpath_empty_string() {
// Test that empty string arguments are rejected with exit code 1
new_ucmd!().arg("").fails().code_is(1);
// Test that empty --relative-base is rejected
new_ucmd!()
.arg("--relative-base=")
.arg("--relative-to=.")
.arg(".")
.fails()
.code_is(1);
// Test that empty --relative-to is rejected
new_ucmd!()
.arg("--relative-to=")
.arg(".")
.fails()
.code_is(1);
}