From 13da7cc4a79ecc191939227ced096ebb6b6048fc Mon Sep 17 00:00:00 2001 From: weili <541602953@qq.com> Date: Fri, 12 Jun 2026 04:51:15 +0000 Subject: [PATCH] find: -printf: advance past a full char in advance_one to avoid a panic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit advance_one read a full `char` via front() but then dropped it by slicing one byte (`&self.string[1..]`). When that char is multibyte (e.g. a `€` after a `%` conversion, a `\` escape, or a `%A`/`%C`/`%T` time directive), byte index 1 is not a char boundary and the slice panics. Advance by the char's UTF-8 length so multibyte input routes through the normal handling instead of aborting. --- src/find/matchers/printf.rs | 12 +++++++++++- tests/test_find.rs | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/find/matchers/printf.rs b/src/find/matchers/printf.rs index 038ec7fb..906e1594 100644 --- a/src/find/matchers/printf.rs +++ b/src/find/matchers/printf.rs @@ -149,7 +149,7 @@ impl FormatStringParser<'_> { fn advance_one(&mut self) -> Result> { let c = self.front()?; - self.string = &self.string[1..]; + self.string = &self.string[c.len_utf8()..]; Ok(c) } @@ -719,6 +719,16 @@ mod tests { ); } + #[test] + fn test_parse_multibyte_char_after_directive() { + assert_eq!( + FormatString::parse("%€").unwrap().components, + vec![FormatComponent::Literal("€".to_owned())] + ); + assert!(FormatString::parse("\\€").is_err()); + assert!(FormatString::parse("%A€").is_err()); + } + #[test] fn test_parse_formatting() { fn unaligned_directive(directive: FormatDirective) -> FormatComponent { diff --git a/tests/test_find.rs b/tests/test_find.rs index cfee583f..997c0bfe 100644 --- a/tests/test_find.rs +++ b/tests/test_find.rs @@ -519,6 +519,22 @@ fn find_printf_octal_escape_before_multibyte_char() { .stdout_only("\0€\n"); } +#[test] +fn find_printf_multibyte_char_after_directive() { + ucmd() + .args(&["./test_data/simple", "-maxdepth", "0", "-printf", "%€\\n"]) + .succeeds() + .stdout_only("€\n"); + ucmd() + .args(&["./test_data/simple", "-maxdepth", "0", "-printf", "\\€\\n"]) + .fails() + .stderr_contains("find: Invalid escape sequence"); + ucmd() + .args(&["./test_data/simple", "-maxdepth", "0", "-printf", "%A€"]) + .fails() + .stderr_contains("find: Invalid time specifier"); +} + #[cfg(unix)] #[test] fn find_perm() {