From ae7e87ddd0cc162f27263a07d6b89f0cd39335be Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Wed, 28 Oct 2026 17:22:28 +0100 Subject: [PATCH] =?UTF-8?q?Skip=20signed=20hex=20codes=20in=20Tagged=20Tex?= =?UTF-8?q?t=20<0x=E2=80=A6>=20tags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found while testing crates/textimport/src/tagged.rs:~190 (the `n.starts_with("0x")` branch of import: `u32::from_str_radix(&n[2..], 16)`) Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/textimport/src/tagged.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/textimport/src/tagged.rs b/crates/textimport/src/tagged.rs index 74806ae4c..e903e98fd 100644 --- a/crates/textimport/src/tagged.rs +++ b/crates/textimport/src/tagged.rs @@ -176,7 +176,10 @@ pub fn import(bytes: &[u8]) -> Result { let (name, value) = tag.split_once(':').map_or((tag.as_str(), ""), |(a, b)| (a, b)); match name { n if n.starts_with("0x") => { - if let Ok(u) = u32::from_str_radix(&n[2..], 16) + // `from_str_radix` accepts a leading '+', so check the digits first. + if let Some(digits) = n.get(2..) + && digits.bytes().all(|b| b.is_ascii_hexdigit()) + && let Ok(u) = u32::from_str_radix(digits, 16) && let Some(ch) = units.push(u) { push(&mut paras, ch, &fmt); @@ -278,6 +281,16 @@ mod tests { assert_eq!(import("\r\nA<0xD83D>B<0xDE00>C".as_bytes()).unwrap().story.text, "ABC"); } + /// `from_str_radix` accepts a leading `+`, so `<0x+41>` inserted an 'A'. A signed or empty + /// code is not a code point tag and is skipped. + #[test] + fn a_signed_hex_code_tag_is_skipped() { + for t in ["<0x+41>", "<0x-41>", "<0x>", "<0xZZ>"] { + let src = format!("\r\nA{t}B"); + assert_eq!(import(src.as_bytes()).unwrap().story.text, "AB", "{t}"); + } + } + /// A size does not have to be non-finite to be nonsense: a file can hold `1e300`, `0` or a /// negative, and tagged text took all three and set them on the text (#1593). They are now /// dropped, as RTF and DOCX already dropped them, so the text keeps its style's size.