Skip to content

Commit 067b296

Browse files
authored
Merge pull request #6993 from jtracey/wc-fixes
wc: fix escaping
2 parents bae4756 + ff8a31e commit 067b296

File tree

4 files changed

+51
-14
lines changed

4 files changed

+51
-14
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ windows = ["feat_os_windows"]
3434
nightly = []
3535
test_unimplemented = []
3636
expensive_tests = []
37+
# "test_risky_names" == enable tests that create problematic file names (would make a network share inaccessible to Windows, breaks SVN on Mac OS, etc.)
38+
test_risky_names = []
3739
# * only build `uudoc` when `--feature uudoc` is activated
3840
uudoc = ["zip", "dep:uuhelp_parser"]
3941
## features

build.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ pub fn main() {
3333
#[allow(clippy::match_same_arms)]
3434
match krate.as_ref() {
3535
"default" | "macos" | "unix" | "windows" | "selinux" | "zip" => continue, // common/standard feature names
36-
"nightly" | "test_unimplemented" | "expensive_tests" => continue, // crate-local custom features
36+
"nightly" | "test_unimplemented" | "expensive_tests" | "test_risky_names" => {
37+
continue
38+
} // crate-local custom features
3739
"uudoc" => continue, // is not a utility
3840
"test" => continue, // over-ridden with 'uu_test' to avoid collision with rust core crate 'test'
3941
s if s.starts_with(FEATURE_PREFIX) => continue, // crate feature sets

src/uu/wc/src/wc.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,17 @@ impl<'a> Input<'a> {
255255
}
256256

257257
/// Converts input to title that appears in stats.
258-
fn to_title(&self) -> Option<Cow<str>> {
258+
fn to_title(&self) -> Option<Cow<OsStr>> {
259259
match self {
260-
Self::Path(path) => Some(match path.to_str() {
261-
Some(s) if !s.contains('\n') => Cow::Borrowed(s),
262-
_ => Cow::Owned(escape_name_wrapper(path.as_os_str())),
263-
}),
264-
Self::Stdin(StdinKind::Explicit) => Some(Cow::Borrowed(STDIN_REPR)),
260+
Self::Path(path) => {
261+
let path = path.as_os_str();
262+
if path.to_string_lossy().contains('\n') {
263+
Some(Cow::Owned(quoting_style::escape_name(path, QS_ESCAPE)))
264+
} else {
265+
Some(Cow::Borrowed(path))
266+
}
267+
}
268+
Self::Stdin(StdinKind::Explicit) => Some(Cow::Borrowed(OsStr::new(STDIN_REPR))),
265269
Self::Stdin(StdinKind::Implicit) => None,
266270
}
267271
}
@@ -852,14 +856,17 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> {
852856
let maybe_title = input.to_title();
853857
let maybe_title_str = maybe_title.as_deref();
854858
if let Err(err) = print_stats(settings, &word_count, maybe_title_str, number_width) {
855-
let title = maybe_title_str.unwrap_or("<stdin>");
856-
show!(err.map_err_context(|| format!("failed to print result for {title}")));
859+
let title = maybe_title_str.unwrap_or(OsStr::new("<stdin>"));
860+
show!(err.map_err_context(|| format!(
861+
"failed to print result for {}",
862+
title.to_string_lossy()
863+
)));
857864
}
858865
}
859866
}
860867

861868
if settings.total_when.is_total_row_visible(num_inputs) {
862-
let title = are_stats_visible.then_some("total");
869+
let title = are_stats_visible.then_some(OsStr::new("total"));
863870
if let Err(err) = print_stats(settings, &total_word_count, title, number_width) {
864871
show!(err.map_err_context(|| "failed to print total".into()));
865872
}
@@ -873,7 +880,7 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> {
873880
fn print_stats(
874881
settings: &Settings,
875882
result: &WordCount,
876-
title: Option<&str>,
883+
title: Option<&OsStr>,
877884
number_width: usize,
878885
) -> io::Result<()> {
879886
let mut stdout = io::stdout().lock();
@@ -893,8 +900,8 @@ fn print_stats(
893900
}
894901

895902
if let Some(title) = title {
896-
writeln!(stdout, "{space}{title}")
897-
} else {
898-
writeln!(stdout)
903+
write!(stdout, "{space}")?;
904+
stdout.write_all(&uucore::os_str_as_bytes_lossy(title))?;
899905
}
906+
writeln!(stdout)
900907
}

tests/by-util/test_wc.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,32 @@ fn test_gnu_compatible_quotation() {
283283
.stdout_is("0 0 0 'some-dir1/12'$'\\n''34.txt'\n");
284284
}
285285

286+
#[cfg(feature = "test_risky_names")]
287+
#[test]
288+
fn test_non_unicode_names() {
289+
let scene = TestScenario::new(util_name!());
290+
let target1 = uucore::os_str_from_bytes(b"some-dir1/1\xC0\n.txt")
291+
.expect("Only unix platforms can test non-unicode names");
292+
let target2 = uucore::os_str_from_bytes(b"some-dir1/2\xC0\t.txt")
293+
.expect("Only unix platforms can test non-unicode names");
294+
let at = &scene.fixtures;
295+
at.mkdir("some-dir1");
296+
at.touch(&target1);
297+
at.touch(&target2);
298+
scene
299+
.ucmd()
300+
.args(&[target1, target2])
301+
.run()
302+
.stdout_is_bytes(
303+
[
304+
b"0 0 0 'some-dir1/1'$'\\300\\n''.txt'\n".to_vec(),
305+
b"0 0 0 some-dir1/2\xC0\t.txt\n".to_vec(),
306+
b"0 0 0 total\n".to_vec(),
307+
]
308+
.concat(),
309+
);
310+
}
311+
286312
#[test]
287313
fn test_multiple_default() {
288314
new_ucmd!()

0 commit comments

Comments
 (0)