From ea72d9d7120f464e3dc7c9694a6fa4218857b161 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:35:45 +0000 Subject: [PATCH] Fix HTML paste panic on bare '&' before multi-byte text Found while testing crates/engine/src/cmd/richclip.rs:307-308 (decode_text) Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/cmd/richclip.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/richclip.rs b/crates/engine/src/cmd/richclip.rs index 280d6746..24bfcd42 100644 --- a/crates/engine/src/cmd/richclip.rs +++ b/crates/engine/src/cmd/richclip.rs @@ -304,7 +304,10 @@ fn decode_text(raw: &str) -> String { while let Some(at) = rest.find('&') { out.push_str(&collapse_space(&rest[..at])); rest = &rest[at..]; - let search_window = rest.len().min(32); + let mut search_window = rest.len().min(32); + while !rest.is_char_boundary(search_window) { + search_window -= 1; + } let Some(end) = rest[..search_window].find(';') else { out.push('&'); rest = &rest[1..]; @@ -361,6 +364,14 @@ fn fold(s: &str) -> String { mod tests { use super::*; + #[test] + fn multibyte_after_ampersand_is_kept() { + let text = "é".repeat(20); + assert_eq!(decode_text(&format!("R&D {text}")), format!("R&D {text}")); + let cjk = "漢".repeat(20); + assert_eq!(decode_text(&format!("&{cjk}")), format!("&{cjk}")); + } + #[test] fn signed_numeric_entities_are_kept_as_typed() { assert_eq!(decode_text("a&#+65;b"), "a&#+65;b");