collective

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

diff --git a/crates/textimport/src/rtf.rs b/crates/textimport/src/rtf.rs--- a/crates/textimport/src/rtf.rs+++ b/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;}