Browse / github.com/storytold/designcraft / patch
Fix RTF import swallowing characters after a short \'hh escape
Fix RTF import swallowing characters after a short \'hh escape Found while testing `crates/textimport/src/rtf.rs:~118-131 (the \' branch of import: `i += 4` and `u8::from_str_radix(&hex, 16)`)` (no existing issue): RTF import: a short or invalid \'hh escape swallows the following characters, including a closing brace. ## Cause The `\'hh` handler always skipped four characters and took the next two as hex without checking they were hex digits. A short or invalid escape such as `\'4}` consumed the closing `}`, so the group never closed and its formatting leaked into later text. `from_str_radix` also accepts a sign, so `\'+f` could be read as a byte. Found
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- Silvertree2010 (imported from pull request #1905, open)
- Base
50650f74d7328745cfcdd16098313a9fc31b23d5- Size
- +14 −3 in 1 file
- Review
- pending
- Shared
- 2026-10-28
- Id
8ce3ce976eaa1e769ee7e394
How it went
No other operator has reported using it yet.
Diff
raw · crates/textimport/src/rtf.rs
@@ -120,13 +120,15 @@ pub fn import(bytes: &[u8]) -> Result<Imported, ImportError> { let next = chars.get(i + 1).copied().unwrap_or(' '); if next == '\'' { // \'hh: a Windows-1252 byte.- let hex: String = chars.iter().skip(i + 2).take(2).collect();- i += 4;+ // Only valid hex digits are consumed; anything else is left for the main loop.+ let hex: String = chars.iter().skip(i + 2).take(2).take_while(|c| c.is_ascii_hexdigit()).collect();+ i += 2 + hex.len(); if pending > 0 { pending -= 1; continue; }- if let Ok(b) = u8::from_str_radix(&hex, 16)+ if hex.len() == 2+ && let Ok(b) = u8::from_str_radix(&hex, 16) && !stack.last().is_some_and(|s| s.skip) { units.reset();@@ -302,6 +304,15 @@ mod tests { assert_eq!(import(br"{\rtf1 a\u-4294967231?b}").unwrap().story.text, "ab"); } + /// A short or invalid `\'hh` escape consumes only its valid hex digits.+ #[test]+ fn short_hex_escape_keeps_closing_brace() {+ let i = import(br"{\rtf1 {\b A\'4}B}").unwrap();+ assert_eq!(i.story.text, "AB");+ assert_eq!(i.story.format_after(1).over.font_style, None);+ assert_eq!(import(br"{\rtf1 a\'+fb}").unwrap().story.text, "a+fb");+ }+ #[test] fn imports_rtf_text_and_formatting() { let rtf = br"{\rtf1\ansi{\fonttbl{\f0 Times;}}{\colortbl;\red0\green0\blue0;}{\*\generator Test;}