Skip to content

Commit d2b95eb

Browse files
authored
Merge pull request #8375 from drinkcat/basename-osstr
basename: Handle non-unicode parameters
2 parents cf3fece + 22d474f commit d2b95eb

File tree

2 files changed

+44
-28
lines changed

2 files changed

+44
-28
lines changed

src/uu/basename/src/basename.rs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66
// spell-checker:ignore (ToDO) fullname
77

8+
use clap::builder::ValueParser;
89
use clap::{Arg, ArgAction, Command};
910
use std::collections::HashMap;
11+
use std::ffi::OsString;
12+
use std::io::{Write, stdout};
1013
use std::path::PathBuf;
1114
use uucore::display::Quotable;
1215
use uucore::error::{UResult, UUsageError};
@@ -24,8 +27,6 @@ pub mod options {
2427

2528
#[uucore::main]
2629
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
27-
let args = args.collect_lossy();
28-
2930
//
3031
// Argument parsing
3132
//
@@ -34,7 +35,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
3435
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
3536

3637
let mut name_args = matches
37-
.get_many::<String>(options::NAME)
38+
.get_many::<OsString>(options::NAME)
3839
.unwrap_or_default()
3940
.collect::<Vec<_>>();
4041
if name_args.is_empty() {
@@ -43,18 +44,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
4344
get_message("basename-error-missing-operand"),
4445
));
4546
}
46-
let multiple_paths =
47-
matches.get_one::<String>(options::SUFFIX).is_some() || matches.get_flag(options::MULTIPLE);
47+
let multiple_paths = matches.get_one::<OsString>(options::SUFFIX).is_some()
48+
|| matches.get_flag(options::MULTIPLE);
4849
let suffix = if multiple_paths {
4950
matches
50-
.get_one::<String>(options::SUFFIX)
51+
.get_one::<OsString>(options::SUFFIX)
5152
.cloned()
5253
.unwrap_or_default()
5354
} else {
5455
// "simple format"
5556
match name_args.len() {
5657
0 => panic!("already checked"),
57-
1 => String::default(),
58+
1 => OsString::default(),
5859
2 => name_args.pop().unwrap().clone(),
5960
_ => {
6061
return Err(UUsageError::new(
@@ -73,7 +74,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
7374
//
7475

7576
for path in name_args {
76-
print!("{}{line_ending}", basename(path, &suffix));
77+
stdout().write_all(&basename(path, &suffix)?)?;
78+
print!("{line_ending}");
7779
}
7880

7981
Ok(())
@@ -96,6 +98,7 @@ pub fn uu_app() -> Command {
9698
.arg(
9799
Arg::new(options::NAME)
98100
.action(ArgAction::Append)
101+
.value_parser(ValueParser::os_string())
99102
.value_hint(clap::ValueHint::AnyPath)
100103
.hide(true)
101104
.trailing_var_arg(true),
@@ -105,6 +108,7 @@ pub fn uu_app() -> Command {
105108
.short('s')
106109
.long(options::SUFFIX)
107110
.value_name("SUFFIX")
111+
.value_parser(ValueParser::os_string())
108112
.help(get_message("basename-help-suffix"))
109113
.overrides_with(options::SUFFIX),
110114
)
@@ -118,21 +122,30 @@ pub fn uu_app() -> Command {
118122
)
119123
}
120124

121-
fn basename(fullname: &str, suffix: &str) -> String {
125+
// We return a Vec<u8>. Returning a seemingly more proper `OsString` would
126+
// require back and forth conversions as we need a &[u8] for printing anyway.
127+
fn basename(fullname: &OsString, suffix: &OsString) -> UResult<Vec<u8>> {
128+
let fullname_bytes = uucore::os_str_as_bytes(fullname)?;
129+
122130
// Handle special case where path ends with /.
123-
if fullname.ends_with("/.") {
124-
return ".".to_string();
131+
if fullname_bytes.ends_with(b"/.") {
132+
return Ok(b".".into());
125133
}
126134

127135
// Convert to path buffer and get last path component
128136
let pb = PathBuf::from(fullname);
129137

130-
pb.components().next_back().map_or_else(String::new, |c| {
131-
let name = c.as_os_str().to_str().unwrap();
138+
pb.components().next_back().map_or(Ok([].into()), |c| {
139+
let name = c.as_os_str();
140+
let name_bytes = uucore::os_str_as_bytes(name)?;
132141
if name == suffix {
133-
name.to_string()
142+
Ok(name_bytes.into())
134143
} else {
135-
name.strip_suffix(suffix).unwrap_or(name).to_string()
144+
let suffix_bytes = uucore::os_str_as_bytes(suffix)?;
145+
Ok(name_bytes
146+
.strip_suffix(suffix_bytes)
147+
.unwrap_or(name_bytes)
148+
.into())
136149
}
137150
})
138151
}

tests/by-util/test_basename.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
// file that was distributed with this source code.
55
// spell-checker:ignore (words) reallylongexecutable nbaz
66

7-
#[cfg(any(unix, target_os = "redox"))]
8-
use std::ffi::OsStr;
97
use uutests::new_ucmd;
108

119
#[test]
@@ -138,20 +136,25 @@ fn test_too_many_args_output() {
138136
.usage_error("extra operand 'c'");
139137
}
140138

141-
#[cfg(any(unix, target_os = "redox"))]
142-
fn test_invalid_utf8_args(os_str: &OsStr) {
143-
let test_vec = vec![os_str.to_os_string()];
144-
new_ucmd!().args(&test_vec).succeeds().stdout_is("fo�o\n");
145-
}
146-
147139
#[cfg(any(unix, target_os = "redox"))]
148140
#[test]
149-
fn invalid_utf8_args_unix() {
150-
use std::os::unix::ffi::OsStrExt;
141+
fn test_invalid_utf8_args() {
142+
let param = uucore::os_str_from_bytes(b"/tmp/some-\xc0-file.k\xf3")
143+
.expect("Only unix platforms can test non-unicode names");
144+
145+
new_ucmd!()
146+
.arg(&param)
147+
.succeeds()
148+
.stdout_is_bytes(b"some-\xc0-file.k\xf3\n");
149+
150+
let suffix = uucore::os_str_from_bytes(b".k\xf3")
151+
.expect("Only unix platforms can test non-unicode names");
151152

152-
let source = [0x66, 0x6f, 0x80, 0x6f];
153-
let os_str = OsStr::from_bytes(&source[..]);
154-
test_invalid_utf8_args(os_str);
153+
new_ucmd!()
154+
.arg(&param)
155+
.arg(&suffix)
156+
.succeeds()
157+
.stdout_is_bytes(b"some-\xc0-file\n");
155158
}
156159

157160
#[test]

0 commit comments

Comments
 (0)