diff --git a/src/find/main.rs b/src/find/main.rs index 1d8787e8..26b4b1de 100644 --- a/src/find/main.rs +++ b/src/find/main.rs @@ -10,7 +10,21 @@ fn main() { // the downstream software of the standard output stream closes the pipe and triggers a panic. uucore::panic::mute_sigpipe_panic(); - let args = std::env::args().collect::>(); + let args = std::env::args_os() + .map(|arg| match arg.into_string() { + Ok(s) => s, + // GNU find treats an invalid-UTF-8 argument as a warning and + // continues with a lossy conversion (exiting 0), rather than + // aborting — so do the same instead of hard-erroring with exit 1. + Err(invalid) => { + eprintln!( + "find: invalid UTF-8 was found in one of the arguments: {}", + invalid.to_string_lossy() + ); + invalid.to_string_lossy().into_owned() + } + }) + .collect::>(); let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect(); let deps = findutils::find::StandardDependencies::new(); std::process::exit(findutils::find::find_main(&strs, &deps)); diff --git a/src/find/matchers/printf.rs b/src/find/matchers/printf.rs index 31151db2..5ad13fea 100644 --- a/src/find/matchers/printf.rs +++ b/src/find/matchers/printf.rs @@ -152,7 +152,11 @@ impl FormatStringParser<'_> { fn advance_one(&mut self) -> Result> { let c = self.front()?; - self.string = &self.string[1..]; + // Slice off one *character*, not one byte: byte slicing `[1..]` panics + // when the next character is multibyte (e.g. a lossy-converted + // replacement char from an invalid-UTF-8 argument, or any non-ASCII + // character following a `%` directive). + self.string = &self.string[c.len_utf8()..]; Ok(c) } diff --git a/tests/test_find.rs b/tests/test_find.rs index ce223d56..4f43062f 100644 --- a/tests/test_find.rs +++ b/tests/test_find.rs @@ -578,6 +578,23 @@ fn find_printf_octal_escape_before_multibyte_char() { .stdout_only("\0€\n"); } +#[cfg(unix)] +#[test] +fn find_printf_invalid_utf8_format_does_not_panic() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + // GNU find treats an invalid-UTF-8 argument as a warning and continues + // (exit 0) with a lossy conversion, so the run succeeds and warns on + // stderr rather than failing with exit 1. + ucmd() + .args(&["./test_data/simple", "-maxdepth", "0"]) + .arg("-printf") + .arg(OsStr::from_bytes(b"%\xff|\n")) + .succeeds() + .stderr_contains("invalid UTF-8"); +} + #[test] fn find_printf_width_too_large() { ucmd()