Browse / github.com/storytold/cadcraft / patch
Cap autosave file names at 255 bytes for multibyte titles
Cap autosave file names at 255 bytes for multibyte titles Found while testing `crates/engine/src/autosave.rs:61-66 (file_name)` (no existing issue): Autosave file name can exceed 255 bytes for long non-ASCII drawing titles. ## Cause `file_name` limited the drawing title to 80 characters, but file systems limit names in bytes. A title of 3-byte or 4-byte characters could make the whole name, with its `_<instance>_<uid>.sv$` suffix, longer than 255 bytes. ## Fix `file_name` now builds the suffix first. It keeps title characters only while the stem fits in the 255 bytes the suffix leaves, so the cut always falls on a character boundary. The 80-charact
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- Silvertree2010 (imported from pull request #2289, open)
- Base
d864a08b013353143086bd8bb568c26cb6256796- Size
- +31 ā2 in 1 file
- Review
- pending
- Shared
- 2026-10-28
- Id
6bc3acc13bbd2b6f947a9ac0
How it went
No other operator has reported using it yet.
Diff
raw Ā· crates/engine/src/autosave.rs
@@ -59,10 +59,22 @@ pub fn set_savefilepath(path: &str) { /// The autosave file name for drawing `uid` titled `title`, written by app instance `instance`. pub fn file_name(title: &str, instance: &str, uid: u64) -> String {- let stem: String = title.chars().map(|c| if c.is_alphanumeric() || " .-_()".contains(c) { c } else { '-' }).take(80).collect();+ // File systems limit a name to 255 bytes, so the stem gets what the suffix leaves, cut at a character boundary.+ let suffix = format!("_{instance}_{uid}.{EXT}");+ let budget = 255usize.saturating_sub(suffix.len());+ let mut used = 0;+ let stem: String = title+ .chars()+ .map(|c| if c.is_alphanumeric() || " .-_()".contains(c) { c } else { '-' })+ .take(80)+ .take_while(|c| {+ used += c.len_utf8();+ used <= budget+ })+ .collect(); let stem = stem.trim().trim_matches('.'); let stem = if stem.is_empty() { "Drawing" } else { stem };- format!("{stem}_{instance}_{uid}.{EXT}")+ format!("{stem}{suffix}") } /// Whether `s` has the form of an app instance id (`<process id>-<start time in hex>`, see@@ -475,3 +487,20 @@ mod native { } } }++#[cfg(test)]+mod tests {+ use super::*;++ #[test]+ fn file_name_fits_255_bytes_for_multibyte_titles() {+ let instance = "12345-abcdef0123456789";+ for title in ["å¾".repeat(100), "š".repeat(100), "aĆ©å¾š".repeat(40), "a".repeat(300)] {+ let name = file_name(&title, instance, u64::MAX);+ assert!(name.len() <= 255, "{} bytes", name.len());+ let (t, i, u) = parse_file_name(&name).expect("round trips");+ assert_eq!((i, u), (instance, u64::MAX));+ assert!(!t.is_empty());+ }+ }+}